• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright 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  #ifndef HDR_PLUS_TYPES_H
17  #define HDR_PLUS_TYPES_H
18  
19  #include <array>
20  #include <stdint.h>
21  #include <string>
22  #include <vector>
23  
24  namespace pbcamera {
25  
26  // This file defines the common types used in HDR+ client and HDR+ service API.
27  
28  typedef int32_t status_t;
29  
30  /*
31   * ImageConfiguration and PlaneConfiguration define the layout of a buffer.
32   * The following is an example of a NV21 buffer.
33   *
34   * <-------Y stride (in bytes)------->
35   * <----width (in pixels)---->
36   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  ^            ^
37   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  |            |
38   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  |            |
39   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  height       Y scanline
40   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  (in lines)   (in lines)
41   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  |            |
42   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  |            |
43   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  |            |
44   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  |            |
45   * Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . .  v            |
46   * . . . . . . . . . . . . . . . . . .               |
47   * . . . . . . . . . . . . . . . . . .               v
48   * <------V/U stride (in bytes)------>
49   * V U V U V U V U V U V U V U . . . .  ^
50   * V U V U V U V U V U V U V U . . . .  |
51   * V U V U V U V U V U V U V U . . . .  |
52   * V U V U V U V U V U V U V U . . . .  V/U scanline
53   * V U V U V U V U V U V U V U . . . .  (in lines)
54   * . . . . . . . . . . . . . . . . . .  |
55   * . . . . . . . . . . . . . . . . . .  v
56   * . . . . . . . . . . . . . . . . . .  -> Image padding.
57   */
58  
59  // PlaneConfiguration defines an image planes configuration.
60  struct PlaneConfiguration {
61      // Number of bytes in each line including padding.
62      uint32_t stride;
63      // Number of lines vertically including padding.
64      uint32_t scanline;
65  
PlaneConfigurationPlaneConfiguration66      PlaneConfiguration() : stride(0), scanline(0) {};
67  
68      bool operator==(const PlaneConfiguration &other) const {
69          return stride == other.stride &&
70                 scanline == other.scanline;
71      }
72  
73      bool operator!=(const PlaneConfiguration &other) const {
74          return !(*this == other);
75      }
76  };
77  
78  // ImageConfiguration defines an image configuration.
79  struct ImageConfiguration {
80      // Image width.
81      uint32_t width;
82      // Image height;
83      uint32_t height;
84      // Image format;
85      int format;
86      // Configuration for each planes.
87      std::vector<PlaneConfiguration> planes;
88      // Number of padded bytes after the last plane.
89      uint32_t padding;
90  
ImageConfigurationImageConfiguration91      ImageConfiguration() : width(0), height(0), format(0), padding(0) {};
92  
93      bool operator==(const ImageConfiguration &other) const {
94          return width == other.width &&
95                 height == other.height &&
96                 format == other.format &&
97                 planes == other.planes &&
98                 padding == other.padding;
99      }
100  
101      bool operator!=(const ImageConfiguration &other) const {
102          return !(*this == other);
103      }
104  };
105  
106  /*
107   * StreamConfiguration defines a stream's configuration, such as its image buffer resolution, used
108   * during stream configuration.
109   */
110  struct StreamConfiguration {
111      /*
112       * Unique ID of the stream. Each stream must have an unique ID so it can be used to identify
113       * the output streams of a StreamBuffer in CaptureRequest.
114       */
115      uint32_t id;
116  
117      // Image configuration.
118      ImageConfiguration image;
119  
120      bool operator==(const StreamConfiguration &other) const {
121          return id == other.id &&
122                 image == other.image;
123      }
124  
125      bool operator!=(const StreamConfiguration &other) const {
126          return !(*this == other);
127      }
128  };
129  
130  /*
131   * SensorMode contains the sensor mode information.
132   */
133  struct SensorMode {
134      // Usually 0 is back camera and 1 is front camera.
135      uint32_t cameraId;
136  
137      // Pixel array resolution.
138      uint32_t pixelArrayWidth;
139      uint32_t pixelArrayHeight;
140  
141      // Active array resolution.
142      uint32_t activeArrayWidth;
143      uint32_t activeArrayHeight;
144  
145      // Sensor output pixel clock.
146      uint32_t outputPixelClkHz;
147  
148      // Sensor timestamp offset due to gyro calibration. When comparing timestamps between AP and
149      // Easel, this offset should be subtracted from AP timestamp.
150      int64_t timestampOffsetNs;
151  
152      // Sensor timestamp offset due to sensor cropping. When comparing timestamps between AP and
153      // Easel, this offset should be subtracted from AP timestamp.
154      int64_t timestampCropOffsetNs;
155  
156      // Sensor output format as defined in android_pixel_format.
157      int format;
158  
SensorModeSensorMode159      SensorMode() : cameraId(0), pixelArrayWidth(0), pixelArrayHeight(0), activeArrayWidth(0),
160                     activeArrayHeight(0), outputPixelClkHz(0) {};
161  };
162  
163  /*
164   * InputConfiguration defines the input configuration for HDR+ service.
165   */
166  struct InputConfiguration {
167      // Whether the input frames come from sensor MIPI or AP. If true, HDR+ service will get input
168      // frames from sensor and sensorMode contains the sensor mode information. If false, HDR+
169      // service will get input frames from AP and streamConfig contains the input stream
170      // configuration.
171      bool isSensorInput;
172      // Sensor mode if isSensorInput is true.
173      SensorMode sensorMode;
174      // Input stream configuration if isSensorInput is false.
175      StreamConfiguration streamConfig;
176  
InputConfigurationInputConfiguration177      InputConfiguration() : isSensorInput(false) {};
178  };
179  
180  /*
181   * StreamBuffer defines a buffer in a stream.
182   */
183  struct StreamBuffer {
184      // ID of the stream that this buffer belongs to.
185      uint32_t streamId;
186      //  DMA buffer fd for this buffer if it's an ION buffer.
187      int32_t dmaBufFd;
188      // Pointer to the data of this buffer.
189      void* data;
190      // Size of the allocated data.
191      uint32_t dataSize;
192  };
193  
194  /*
195   * CaptureRequest defines a capture request that HDR+ client sends to HDR+ service.
196   */
197  struct CaptureRequest {
198      /*
199       * ID of the capture request. Each capture request must have an unique ID. When HDR+ service
200       * sends a CaptureResult to HDR+ client for this request, CaptureResult.requestId will be
201       * assigned to this ID.
202       */
203      uint32_t id;
204      /*
205       * Output buffers of the request. The buffers will be filled with captured image when HDR+
206       * service sends the output buffers in CaptureResult.
207       */
208      std::vector<StreamBuffer> outputBuffers;
209  };
210  
211  // Util functions used in StaticMetadata and FrameMetadata.
212  namespace metadatautils {
213  template<typename T>
214  void appendValueToString(std::string *strOut, const char* key, T value);
215  
216  template<typename T>
217  void appendVectorOrArrayToString(std::string *strOut, T values);
218  
219  template<typename T>
220  void appendVectorOrArrayToString(std::string *strOut, const char* key, T values);
221  
222  template<typename T, size_t SIZE>
223  void appendVectorArrayToString(std::string *strOut, const char* key,
224              std::vector<std::array<T, SIZE>> values);
225  
226  template<typename T, size_t SIZE>
227  void appendArrayArrayToString(std::string *strOut, const char* key,
228              std::array<T, SIZE> values);
229  } // namespace metadatautils
230  
231  static const uint32_t DEBUG_PARAM_NONE                      = 0u;
232  static const uint32_t DEBUG_PARAM_SAVE_GCAME_INPUT_METERING = (1u);
233  static const uint32_t DEBUG_PARAM_SAVE_GCAME_INPUT_PAYLOAD  = (1u << 1);
234  static const uint32_t DEBUG_PARAM_SAVE_GCAME_TEXT           = (1u << 2);
235  static const uint32_t DEBUG_PARAM_SAVE_PROFILE              = (1u << 3);
236  static const uint32_t DEBUG_PARAM_SAVE_GCAME_IPU_WATERMARK  = (1u << 4);
237  
238  /*
239   * StaticMetadata defines a camera device's characteristics.
240   *
241   * If this structure is changed, serialization in MessengerToHdrPlusService and deserialization in
242   * MessengerListenerFromHdrPlusClient should also be updated.
243   */
244  struct StaticMetadata {
245      // The following are from Android Camera Metadata
246      uint8_t flashInfoAvailable; // android.flash.info.available
247      std::array<int32_t, 2> sensitivityRange; // android.sensor.info.sensitivityRange
248      int32_t maxAnalogSensitivity; // android.sensor.maxAnalogSensitivity
249      std::array<int32_t, 2> pixelArraySize; // android.sensor.info.pixelArraySize
250      std::array<int32_t, 4> activeArraySize; // android.sensor.info.activeArraySize
251      std::vector<std::array<int32_t, 4>> opticalBlackRegions; // android.sensor.opticalBlackRegions
252      // android.scaler.availableStreamConfigurations
253      std::vector<std::array<int32_t, 4>> availableStreamConfigurations;
254      uint8_t referenceIlluminant1; // android.sensor.referenceIlluminant1
255      uint8_t referenceIlluminant2; // android.sensor.referenceIlluminant2
256      std::array<float, 9> calibrationTransform1; // android.sensor.calibrationTransform1
257      std::array<float, 9> calibrationTransform2; // android.sensor.calibrationTransform2
258      std::array<float, 9> colorTransform1; // android.sensor.colorTransform1
259      std::array<float, 9> colorTransform2; // android.sensor.colorTransform2
260      int32_t whiteLevel; // android.sensor.info.whiteLevel
261      uint8_t colorFilterArrangement; // android.sensor.info.colorFilterArrangement
262      std::vector<float> availableApertures; // android.lens.info.availableApertures
263      std::vector<float> availableFocalLengths; // android.lens.info.availableFocalLengths
264      std::array<int32_t, 2> shadingMapSize; // android.lens.info.shadingMapSize
265      uint8_t focusDistanceCalibration; // android.lens.info.focusDistanceCalibration
266      std::array<int32_t, 2> aeCompensationRange; // android.control.aeCompensationRange
267      float aeCompensationStep; // android.control.aeCompensationStep
268  
269      uint32_t debugParams; // Use HDRPLUS_DEBUG_PARAM_*
270  
271      // Convert this static metadata to a string and append it to the specified string.
appendToStringStaticMetadata272      void appendToString(std::string *strOut) const {
273          if (strOut == nullptr) return;
274  
275          metadatautils::appendValueToString(strOut, "flashInfoAvailable", flashInfoAvailable);
276          metadatautils::appendVectorOrArrayToString(strOut, "sensitivityRange", sensitivityRange);
277          metadatautils::appendValueToString(strOut, "maxAnalogSensitivity", maxAnalogSensitivity);
278          metadatautils::appendVectorOrArrayToString(strOut, "pixelArraySize", pixelArraySize);
279          metadatautils::appendVectorOrArrayToString(strOut, "activeArraySize", activeArraySize);
280          metadatautils::appendVectorArrayToString(strOut, "opticalBlackRegions",
281                  opticalBlackRegions);
282          metadatautils::appendVectorArrayToString(strOut, "availableStreamConfigurations",
283                  availableStreamConfigurations);
284          metadatautils::appendValueToString(strOut, "referenceIlluminant1", referenceIlluminant1);
285          metadatautils::appendValueToString(strOut, "referenceIlluminant2", referenceIlluminant2);
286          metadatautils::appendVectorOrArrayToString(strOut, "calibrationTransform1",
287                  calibrationTransform1);
288          metadatautils::appendVectorOrArrayToString(strOut, "calibrationTransform2",
289                  calibrationTransform2);
290          metadatautils::appendVectorOrArrayToString(strOut, "colorTransform1", colorTransform1);
291          metadatautils::appendVectorOrArrayToString(strOut, "colorTransform2", colorTransform2);
292          metadatautils::appendValueToString(strOut, "whiteLevel", whiteLevel);
293          metadatautils::appendValueToString(strOut, "colorFilterArrangement",
294                  colorFilterArrangement);
295          metadatautils::appendVectorOrArrayToString(strOut, "availableApertures",
296                  availableApertures);
297          metadatautils::appendVectorOrArrayToString(strOut, "availableFocalLengths",
298                  availableFocalLengths);
299          metadatautils::appendVectorOrArrayToString(strOut, "shadingMapSize", shadingMapSize);
300          metadatautils::appendValueToString(strOut, "focusDistanceCalibration",
301                  focusDistanceCalibration);
302          metadatautils::appendVectorOrArrayToString(strOut, "aeCompensationRange",
303                  aeCompensationRange);
304          metadatautils::appendValueToString(strOut, "aeCompensationStep",
305                  aeCompensationStep);
306          metadatautils::appendValueToString(strOut, "debugParams", debugParams);
307      }
308  };
309  
310  /*
311   * FrameMetadata defines properties of a frame captured on AP.
312   *
313   * If this structure is changed, serialization in MessengerToHdrPlusService and deserialization in
314   * MessengerListenerFromHdrPlusClient should also be updated.
315   */
316  struct FrameMetadata {
317      int64_t easelTimestamp; // Easel timestamp
318  
319      // The following are from Android Camera Metadata
320      int64_t exposureTime; // android.sensor.exposureTime
321      int32_t sensitivity; // android.sensor.sensitivity
322      int32_t postRawSensitivityBoost; // android.control.postRawSensitivityBoost
323      uint8_t flashMode; // android.flash.mode
324      std::array<float, 4> colorCorrectionGains; // android.colorCorrection.gains
325      std::array<float, 9> colorCorrectionTransform; // android.colorCorrection.transform
326      std::array<float, 3> neutralColorPoint; // android.sensor.neutralColorPoint
327      int64_t timestamp; // android.sensor.timestamp
328      uint8_t blackLevelLock; // android.blackLevel.lock
329      uint8_t faceDetectMode; // android.statistics.faceDetectMode
330      std::vector<int32_t> faceIds; // android.statistics.faceIds
331      std::vector<std::array<int32_t, 6>> faceLandmarks; // android.statistics.faceLandmarks
332      std::vector<std::array<int32_t, 4>> faceRectangles; // android.statistics.faceRectangles
333      std::vector<uint8_t> faceScores; // android.statistics.faceScores
334      uint8_t sceneFlicker; // android.statistics.sceneFlicker
335      std::array<std::array<double, 2>, 4> noiseProfile; // android.sensor.noiseProfile
336      std::array<float, 4> dynamicBlackLevel; // android.sensor.dynamicBlackLevel
337      std::vector<float> lensShadingMap; // android.statistics.lensShadingMap
338      float focusDistance; // android.lens.focusDistance
339      int32_t aeExposureCompensation; // android.control.aeExposureCompensation
340      uint8_t aeMode; // android.control.aeMode
341      uint8_t aeLock; // android.control.aeLock
342      uint8_t aeState; // android.control.aeState
343      uint8_t aePrecaptureTrigger; // android.control.aePrecaptureTrigger
344      std::vector<std::array<int32_t, 5>> aeRegions; // android.control.aeRegions
345  
346      // Convert this static metadata to a string and append it to the specified string.
appendToStringFrameMetadata347      void appendToString(std::string *strOut) const {
348          if (strOut == nullptr) return;
349  
350          metadatautils::appendValueToString(strOut, "easelTimestamp", easelTimestamp);
351          metadatautils::appendValueToString(strOut, "exposureTime", exposureTime);
352          metadatautils::appendValueToString(strOut, "sensitivity", sensitivity);
353          metadatautils::appendValueToString(strOut, "postRawSensitivityBoost",
354                  postRawSensitivityBoost);
355          metadatautils::appendValueToString(strOut, "flashMode", flashMode);
356          metadatautils::appendVectorOrArrayToString(strOut, "colorCorrectionGains",
357                  colorCorrectionGains);
358          metadatautils::appendVectorOrArrayToString(strOut, "colorCorrectionTransform",
359                  colorCorrectionTransform);
360          metadatautils::appendVectorOrArrayToString(strOut, "neutralColorPoint", neutralColorPoint);
361          metadatautils::appendValueToString(strOut, "timestamp", timestamp);
362          metadatautils::appendValueToString(strOut, "blackLevelLock", blackLevelLock);
363          metadatautils::appendValueToString(strOut, "faceDetectMode", faceDetectMode);
364          metadatautils::appendVectorOrArrayToString(strOut, "faceIds", faceIds);
365          metadatautils::appendVectorArrayToString(strOut, "faceLandmarks", faceLandmarks);
366          metadatautils::appendVectorArrayToString(strOut, "faceRectangles", faceRectangles);
367          metadatautils::appendVectorOrArrayToString(strOut, "faceScores", faceScores);
368          metadatautils::appendArrayArrayToString(strOut, "noiseProfile", noiseProfile);
369          metadatautils::appendValueToString(strOut, "sceneFlicker", sceneFlicker);
370          metadatautils::appendVectorOrArrayToString(strOut, "dynamicBlackLevel", dynamicBlackLevel);
371          metadatautils::appendVectorOrArrayToString(strOut, "lensShadingMap", lensShadingMap);
372          metadatautils::appendValueToString(strOut, "focusDistance", focusDistance);
373          metadatautils::appendValueToString(strOut, "aeExposureCompensation", aeExposureCompensation);
374          metadatautils::appendValueToString(strOut, "aeMode", aeMode);
375          metadatautils::appendValueToString(strOut, "aeLock", aeLock);
376          metadatautils::appendValueToString(strOut, "aeState", aeState);
377          metadatautils::appendValueToString(strOut, "aePrecaptureTrigger", aePrecaptureTrigger);
378          metadatautils::appendVectorArrayToString(strOut, "aeRegions", aeRegions);
379      }
380  };
381  
382  /*
383   * RequestMetadata defines the properties for a capture request.
384   *
385   * If this structure is changed, serialization in MessengerToHdrPlusClient and deserialization in
386   * MessengerListenerFromHdrPlusService should also be updated.
387   */
388  struct RequestMetadata {
389      std::array<int32_t, 4> cropRegion; // android.scaler.cropRegion (x_min, y_min, width, height)
390      int32_t aeExposureCompensation; // android.control.aeExposureCompensation
391  
392      bool postviewEnable; // com.google.nexus.experimental2017.stats.postview_enable
393      bool continuousCapturing; // Whether to capture RAW while HDR+ processing.
394  
395      // Convert this static metadata to a string and append it to the specified string.
appendToStringRequestMetadata396      void appendToString(std::string *strOut) const {
397          if (strOut == nullptr) return;
398          metadatautils::appendVectorOrArrayToString(strOut, "cropRegion", cropRegion);
399          metadatautils::appendValueToString(strOut, "aeExposureCompensation", aeExposureCompensation);
400          metadatautils::appendValueToString(strOut, "postviewEnable", postviewEnable);
401          metadatautils::appendValueToString(strOut, "continuousCapturing", continuousCapturing);
402      }
403  };
404  
405  /*
406   * ResultMetadata defines a process frame's properties that have been modified due to processing.
407   *
408   * If this structure is changed, serialization in MessengerToHdrPlusClient and deserialization in
409   * MessengerListenerFromHdrPlusService should also be updated.
410   */
411  struct ResultMetadata {
412      int64_t easelTimestamp; // Easel timestamp of SOF of the base frame.
413      int64_t timestamp; // android.sensor.timestamp. AP timestamp of exposure start of the base
414                         // frame.
415      std::string makernote; // Obfuscated capture information.
416  
417      // Convert this static metadata to a string and append it to the specified string.
appendToStringResultMetadata418      void appendToString(std::string *strOut) const {
419          if (strOut == nullptr) return;
420          metadatautils::appendValueToString(strOut, "easelTimestamp", easelTimestamp);
421          metadatautils::appendValueToString(strOut, "timestamp", timestamp);
422          metadatautils::appendValueToString(strOut, "makernote", makernote.size());
423      }
424  };
425  
426  /*
427   * CaptureResult defines a capture result that HDR+ service returns to HDR+ client.
428   */
429  struct CaptureResult {
430      /*
431       * ID of the CaptureRequest that this capture result corresponds to. It can be used to match
432       * the original CaptureRequest when the HDR+ client receives this result.
433       */
434      uint32_t requestId;
435      /*
436       * Output buffers filled with processed frame by HDR+ service.
437       */
438      std::vector<StreamBuffer> outputBuffers;
439  
440      /*
441       * Result metadata including modified properties due to processing.
442       */
443      ResultMetadata metadata;
444  };
445  
446  // Util functions used in StaticMetadata and FrameMetadata.
447  namespace metadatautils {
448  
449  /*
450   * Append a key and a value to a string.
451   *
452   * strOut is the string to append a key and a value to.
453   * key is the name of the data.
454   * value is the value of the data.
455   */
456  template<typename T>
appendValueToString(std::string * strOut,const char * key,T value)457  void appendValueToString(std::string *strOut, const char* key, T value) {
458      if (strOut == nullptr) return;
459      (*strOut) += std::string(key) + ": " + std::to_string(value) + "\n";
460  }
461  
462  /*
463   * Append a vector or an array of values to a string.
464   *
465   * strOut is the string to append a key and values to.
466   * values is a vector or an array containing values to append to the string.
467   */
468  template<typename T>
appendVectorOrArrayToString(std::string * strOut,T values)469  void appendVectorOrArrayToString(std::string *strOut, T values) {
470      if (strOut == nullptr) return;
471      for (size_t i = 0; i < values.size(); i++) {
472          (*strOut) += std::to_string(values[i]);
473          if (i != values.size() - 1) {
474              (*strOut) +=", ";
475          }
476      }
477  }
478  
479  /*
480   * Append a key and a vector or an array of values to a string.
481   *
482   * strOut is the string to append a key and values to.
483   * key is the name of the data.
484   * values is a vector or an array containing values to append to the string.
485   */
486  template<typename T>
appendVectorOrArrayToString(std::string * strOut,const char * key,T values)487  void appendVectorOrArrayToString(std::string *strOut, const char* key, T values) {
488      if (strOut == nullptr) return;
489      (*strOut) += std::string(key) + ": ";
490      appendVectorOrArrayToString(strOut, values);
491      (*strOut) += "\n";
492  }
493  
494  /*
495   * Append a key and a vector of arrays to a string.
496   *
497   * strOut is the string to append a key and values to.
498   * key is the name of the data.
499   * values is a vector of arrays containing values to append to the string.
500   */
501  template<typename T, size_t SIZE>
appendVectorArrayToString(std::string * strOut,const char * key,std::vector<std::array<T,SIZE>> values)502  void appendVectorArrayToString(std::string *strOut, const char* key,
503              std::vector<std::array<T, SIZE>> values) {
504      if (strOut == nullptr) return;
505      (*strOut) += std::string(key) + ": ";
506      for (size_t i = 0; i < values.size(); i++) {
507          appendVectorOrArrayToString(strOut, values[i]);
508          if (i != values.size() - 1) {
509              (*strOut) +=", ";
510          }
511      }
512      (*strOut) += "\n";
513  }
514  
515  /*
516   * Append a key and an array of arrays to a string.
517   *
518   * strOut is the string to append a key and values to.
519   * key is the name of the data.
520   * values is an array of arrays containing values to append to the string.
521   */
522  template<typename T, size_t SIZE>
appendArrayArrayToString(std::string * strOut,const char * key,std::array<T,SIZE> values)523  void appendArrayArrayToString(std::string *strOut, const char* key,
524              std::array<T, SIZE> values) {
525      if (strOut == nullptr) return;
526      (*strOut) += std::string(key) + ": ";
527      for (size_t i = 0; i < values.size(); i++) {
528          appendVectorOrArrayToString(strOut, values[i]);
529          if (i != values.size() - 1) {
530              (*strOut) +=", ";
531          }
532      }
533      (*strOut) += "\n";
534  }
535  
536  } // namespace metadatautils
537  
538  } // namespace pbcamera
539  
540  #endif // HDR_PLUS_TYPES_H
541