source: 3DVCSoftware/branches/HTM-DEV-0.1-dev/source/Lib/TLibEncoder/SEIwrite.cpp @ 331

Last change on this file since 331 was 324, checked in by tech, 12 years ago

Initial development version for update to latest HM version.
Includes MV-HEVC and basic extensions for 3D-HEVC.

  • Property svn:eol-style set to native
File size: 17.1 KB
Line 
1/* The copyright in this software is being made available under the BSD
2 * License, included below. This software may be subject to other third party
3 * and contributor rights, including patent rights, and no such rights are
4 * granted under this license.
5 *
6 * Copyright (c) 2010-2013, ITU/ISO/IEC
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 *
12 *  * Redistributions of source code must retain the above copyright notice,
13 *    this list of conditions and the following disclaimer.
14 *  * Redistributions in binary form must reproduce the above copyright notice,
15 *    this list of conditions and the following disclaimer in the documentation
16 *    and/or other materials provided with the distribution.
17 *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
18 *    be used to endorse or promote products derived from this software without
19 *    specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
31 * THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include "TLibCommon/TComBitCounter.h"
35#include "TLibCommon/TComBitStream.h"
36#include "TLibCommon/SEI.h"
37#include "TLibCommon/TComSlice.h"
38#include "SEIwrite.h"
39
40//! \ingroup TLibEncoder
41//! \{
42
43#if ENC_DEC_TRACE
44Void  xTraceSEIHeader()
45{
46  fprintf( g_hTrace, "=========== SEI message ===========\n");
47}
48
49Void  xTraceSEIMessageType(SEI::PayloadType payloadType)
50{
51  switch (payloadType)
52  {
53  case SEI::DECODED_PICTURE_HASH:
54    fprintf( g_hTrace, "=========== Decoded picture hash SEI message ===========\n");
55    break;
56  case SEI::USER_DATA_UNREGISTERED:
57    fprintf( g_hTrace, "=========== User Data Unregistered SEI message ===========\n");
58    break;
59  case SEI::ACTIVE_PARAMETER_SETS:
60    fprintf( g_hTrace, "=========== Active Parameter sets SEI message ===========\n");
61    break;
62  case SEI::BUFFERING_PERIOD:
63    fprintf( g_hTrace, "=========== Buffering period SEI message ===========\n");
64    break;
65  case SEI::PICTURE_TIMING:
66    fprintf( g_hTrace, "=========== Picture timing SEI message ===========\n");
67    break;
68  case SEI::RECOVERY_POINT:
69    fprintf( g_hTrace, "=========== Recovery point SEI message ===========\n");
70    break;
71  case SEI::FRAME_PACKING:
72    fprintf( g_hTrace, "=========== Frame Packing Arrangement SEI message ===========\n");
73    break;
74  case SEI::DISPLAY_ORIENTATION:
75    fprintf( g_hTrace, "=========== Display Orientation SEI message ===========\n");
76    break;
77  case SEI::TEMPORAL_LEVEL0_INDEX:
78    fprintf( g_hTrace, "=========== Temporal Level Zero Index SEI message ===========\n");
79    break;
80  case SEI::REGION_REFRESH_INFO:
81    fprintf( g_hTrace, "=========== Gradual Decoding Refresh Information SEI message ===========\n");
82    break;
83  case SEI::DECODING_UNIT_INFO:
84    fprintf( g_hTrace, "=========== Decoding Unit Information SEI message ===========\n");
85    break;
86  default:
87    fprintf( g_hTrace, "=========== Unknown SEI message ===========\n");
88    break;
89  }
90}
91#endif
92
93void SEIWriter::xWriteSEIpayloadData(const SEI& sei, TComSPS *sps)
94{
95  switch (sei.payloadType())
96  {
97  case SEI::USER_DATA_UNREGISTERED:
98    xWriteSEIuserDataUnregistered(*static_cast<const SEIuserDataUnregistered*>(&sei));
99    break;
100  case SEI::ACTIVE_PARAMETER_SETS:
101    xWriteSEIActiveParameterSets(*static_cast<const SEIActiveParameterSets*>(& sei)); 
102    break; 
103  case SEI::DECODING_UNIT_INFO:
104    xWriteSEIDecodingUnitInfo(*static_cast<const SEIDecodingUnitInfo*>(& sei), sps);
105    break;
106  case SEI::DECODED_PICTURE_HASH:
107    xWriteSEIDecodedPictureHash(*static_cast<const SEIDecodedPictureHash*>(&sei));
108    break;
109  case SEI::BUFFERING_PERIOD:
110    xWriteSEIBufferingPeriod(*static_cast<const SEIBufferingPeriod*>(&sei), sps);
111    break;
112  case SEI::PICTURE_TIMING:
113    xWriteSEIPictureTiming(*static_cast<const SEIPictureTiming*>(&sei), sps);
114    break;
115  case SEI::RECOVERY_POINT:
116    xWriteSEIRecoveryPoint(*static_cast<const SEIRecoveryPoint*>(&sei));
117    break;
118  case SEI::FRAME_PACKING:
119    xWriteSEIFramePacking(*static_cast<const SEIFramePacking*>(&sei));
120    break;
121  case SEI::DISPLAY_ORIENTATION:
122    xWriteSEIDisplayOrientation(*static_cast<const SEIDisplayOrientation*>(&sei));
123    break;
124  case SEI::TEMPORAL_LEVEL0_INDEX:
125    xWriteSEITemporalLevel0Index(*static_cast<const SEITemporalLevel0Index*>(&sei));
126    break;
127  case SEI::REGION_REFRESH_INFO:
128    xWriteSEIGradualDecodingRefreshInfo(*static_cast<const SEIGradualDecodingRefreshInfo*>(&sei));
129    break;
130  default:
131    assert(!"Unhandled SEI message");
132  }
133}
134
135/**
136 * marshal a single SEI message sei, storing the marshalled representation
137 * in bitstream bs.
138 */
139Void SEIWriter::writeSEImessage(TComBitIf& bs, const SEI& sei, TComSPS *sps)
140{
141  /* calculate how large the payload data is */
142  /* TODO: this would be far nicer if it used vectored buffers */
143  TComBitCounter bs_count;
144  bs_count.resetBits();
145  setBitstream(&bs_count);
146
147#if ENC_DEC_TRACE
148  g_HLSTraceEnable = false;
149#endif
150  xWriteSEIpayloadData(sei, sps);
151#if ENC_DEC_TRACE
152  g_HLSTraceEnable = true;
153#endif
154  UInt payload_data_num_bits = bs_count.getNumberOfWrittenBits();
155  assert(0 == payload_data_num_bits % 8);
156
157  setBitstream(&bs);
158
159#if ENC_DEC_TRACE
160  xTraceSEIHeader();
161#endif
162
163  UInt payloadType = sei.payloadType();
164  for (; payloadType >= 0xff; payloadType -= 0xff)
165  {
166    WRITE_CODE(0xff, 8, "payload_type");
167  }
168  WRITE_CODE(payloadType, 8, "payload_type");
169
170  UInt payloadSize = payload_data_num_bits/8;
171  for (; payloadSize >= 0xff; payloadSize -= 0xff)
172  {
173    WRITE_CODE(0xff, 8, "payload_size");
174  }
175  WRITE_CODE(payloadSize, 8, "payload_size");
176
177  /* payloadData */
178#if ENC_DEC_TRACE
179  xTraceSEIMessageType(sei.payloadType());
180#endif
181
182  xWriteSEIpayloadData(sei, sps);
183}
184
185/**
186 * marshal a user_data_unregistered SEI message sei, storing the marshalled
187 * representation in bitstream bs.
188 */
189Void SEIWriter::xWriteSEIuserDataUnregistered(const SEIuserDataUnregistered &sei)
190{
191  for (UInt i = 0; i < 16; i++)
192  {
193    WRITE_CODE(sei.uuid_iso_iec_11578[i], 8 , "sei.uuid_iso_iec_11578[i]");
194  }
195
196  for (UInt i = 0; i < sei.userDataLength; i++)
197  {
198    WRITE_CODE(sei.userData[i], 8 , "user_data");
199  }
200}
201
202/**
203 * marshal a decoded picture hash SEI message, storing the marshalled
204 * representation in bitstream bs.
205 */
206Void SEIWriter::xWriteSEIDecodedPictureHash(const SEIDecodedPictureHash& sei)
207{
208  UInt val;
209
210  WRITE_CODE(sei.method, 8, "hash_type");
211
212  for(Int yuvIdx = 0; yuvIdx < 3; yuvIdx++)
213  {
214    if(sei.method == SEIDecodedPictureHash::MD5)
215    {
216      for (UInt i = 0; i < 16; i++)
217      {
218        WRITE_CODE(sei.digest[yuvIdx][i], 8, "picture_md5");
219      }
220    }
221    else if(sei.method == SEIDecodedPictureHash::CRC)
222    {
223      val = (sei.digest[yuvIdx][0] << 8)  + sei.digest[yuvIdx][1];
224      WRITE_CODE(val, 16, "picture_crc");
225    }
226    else if(sei.method == SEIDecodedPictureHash::CHECKSUM)
227    {
228      val = (sei.digest[yuvIdx][0] << 24)  + (sei.digest[yuvIdx][1] << 16) + (sei.digest[yuvIdx][2] << 8) + sei.digest[yuvIdx][3];
229      WRITE_CODE(val, 32, "picture_checksum");
230    }
231  }
232}
233
234Void SEIWriter::xWriteSEIActiveParameterSets(const SEIActiveParameterSets& sei)
235{
236  WRITE_CODE(sei.activeVPSId,     4, "active_vps_id");
237#if L0047_APS_FLAGS
238  WRITE_FLAG(sei.m_fullRandomAccessFlag, "full_random_access_flag");
239  WRITE_FLAG(sei.m_noParamSetUpdateFlag, "no_param_set_update_flag");
240#endif
241  WRITE_UVLC(sei.numSpsIdsMinus1,    "num_sps_ids_minus1");
242
243  assert (sei.activeSeqParamSetId.size() == (sei.numSpsIdsMinus1 + 1));
244
245  for (Int i = 0; i < sei.activeSeqParamSetId.size(); i++)
246  {
247    WRITE_UVLC(sei.activeSeqParamSetId[i], "active_seq_param_set_id"); 
248  }
249
250  UInt uiBits = m_pcBitIf->getNumberOfWrittenBits();
251  UInt uiAlignedBits = ( 8 - (uiBits&7) ) % 8; 
252  if(uiAlignedBits) 
253  {
254    WRITE_FLAG(1, "alignment_bit" );
255    uiAlignedBits--; 
256    while(uiAlignedBits--)
257    {
258      WRITE_FLAG(0, "alignment_bit" );
259    }
260  }
261}
262
263Void SEIWriter::xWriteSEIDecodingUnitInfo(const SEIDecodingUnitInfo& sei, TComSPS *sps)
264{
265  TComVUI *vui = sps->getVuiParameters();
266  WRITE_UVLC(sei.m_decodingUnitIdx, "decoding_unit_idx");
267  if(vui->getHrdParameters()->getSubPicCpbParamsInPicTimingSEIFlag())
268  {
269    WRITE_CODE( sei.m_duSptCpbRemovalDelay, (vui->getHrdParameters()->getDuCpbRemovalDelayLengthMinus1() + 1), "du_spt_cpb_removal_delay");
270  }
271#if L0044_DU_DPB_OUTPUT_DELAY_HRD
272  WRITE_FLAG( sei.m_dpbOutputDuDelayPresentFlag, "dpb_output_du_delay_present_flag");
273  if(sei.m_dpbOutputDuDelayPresentFlag)
274  {
275    WRITE_CODE(sei.m_picSptDpbOutputDuDelay, vui->getHrdParameters()->getDpbOutputDelayDuLengthMinus1() + 1, "pic_spt_dpb_output_du_delay");
276  }
277#endif
278  xWriteByteAlign();
279}
280
281Void SEIWriter::xWriteSEIBufferingPeriod(const SEIBufferingPeriod& sei, TComSPS *sps)
282{
283  Int i, nalOrVcl;
284  TComVUI *vui = sps->getVuiParameters();
285  TComHRD *hrd = vui->getHrdParameters();
286
287  WRITE_UVLC( sei.m_bpSeqParameterSetId, "bp_seq_parameter_set_id" );
288  if( !hrd->getSubPicCpbParamsPresentFlag() )
289  {
290    WRITE_FLAG( sei.m_rapCpbParamsPresentFlag, "rap_cpb_params_present_flag" );
291  }
292#if L0328_SPLICING
293  WRITE_FLAG( sei.m_concatenationFlag, "concatenation_flag");
294  WRITE_CODE( sei.m_auCpbRemovalDelayDelta - 1, ( hrd->getCpbRemovalDelayLengthMinus1() + 1 ), "au_cpb_removal_delay_delta_minus1" );
295#endif
296#if L0044_CPB_DPB_DELAY_OFFSET
297  if( sei.m_rapCpbParamsPresentFlag )
298  {
299    WRITE_CODE( sei.m_cpbDelayOffset, hrd->getCpbRemovalDelayLengthMinus1() + 1, "cpb_delay_offset" );
300    WRITE_CODE( sei.m_dpbDelayOffset, hrd->getDpbOutputDelayLengthMinus1()  + 1, "dpb_delay_offset" );
301  }
302#endif
303  for( nalOrVcl = 0; nalOrVcl < 2; nalOrVcl ++ )
304  {
305    if( ( ( nalOrVcl == 0 ) && ( hrd->getNalHrdParametersPresentFlag() ) ) ||
306        ( ( nalOrVcl == 1 ) && ( hrd->getVclHrdParametersPresentFlag() ) ) )
307    {
308      for( i = 0; i < ( hrd->getCpbCntMinus1( 0 ) + 1 ); i ++ )
309      {
310        WRITE_CODE( sei.m_initialCpbRemovalDelay[i][nalOrVcl],( hrd->getInitialCpbRemovalDelayLengthMinus1() + 1 ) ,           "initial_cpb_removal_delay" );
311        WRITE_CODE( sei.m_initialCpbRemovalDelayOffset[i][nalOrVcl],( hrd->getInitialCpbRemovalDelayLengthMinus1() + 1 ),      "initial_cpb_removal_delay_offset" );
312        if( hrd->getSubPicCpbParamsPresentFlag() || sei.m_rapCpbParamsPresentFlag )
313        {
314          WRITE_CODE( sei.m_initialAltCpbRemovalDelay[i][nalOrVcl], ( hrd->getInitialCpbRemovalDelayLengthMinus1() + 1 ) ,     "initial_alt_cpb_removal_delay" );
315          WRITE_CODE( sei.m_initialAltCpbRemovalDelayOffset[i][nalOrVcl], ( hrd->getInitialCpbRemovalDelayLengthMinus1() + 1 ),"initial_alt_cpb_removal_delay_offset" );
316        }
317      }
318    }
319  }
320  xWriteByteAlign();
321}
322Void SEIWriter::xWriteSEIPictureTiming(const SEIPictureTiming& sei,  TComSPS *sps)
323{
324  Int i;
325  TComVUI *vui = sps->getVuiParameters();
326  TComHRD *hrd = vui->getHrdParameters();
327
328#if !L0045_CONDITION_SIGNALLING
329  // This condition was probably OK before the pic_struct, progressive_source_idc, duplicate_flag were added
330  if( !hrd->getNalHrdParametersPresentFlag() && !hrd->getVclHrdParametersPresentFlag() )
331    return;
332#endif
333  if( vui->getFrameFieldInfoPresentFlag() )
334  {
335    WRITE_CODE( sei.m_picStruct, 4,              "pic_struct" );
336#if L0046_RENAME_PROG_SRC_IDC
337    WRITE_CODE( sei.m_sourceScanType, 2,         "source_scan_type" );
338#else
339    WRITE_CODE( sei.m_progressiveSourceIdc, 2,   "progressive_source_idc" );
340#endif
341    WRITE_FLAG( sei.m_duplicateFlag ? 1 : 0,     "duplicate_flag" );
342  }
343
344#if L0045_CONDITION_SIGNALLING
345  if( hrd->getCpbDpbDelaysPresentFlag() )
346  {
347#endif
348    WRITE_CODE( sei.m_auCpbRemovalDelay - 1, ( hrd->getCpbRemovalDelayLengthMinus1() + 1 ),                                         "au_cpb_removal_delay_minus1" );
349    WRITE_CODE( sei.m_picDpbOutputDelay, ( hrd->getDpbOutputDelayLengthMinus1() + 1 ),                                          "pic_dpb_output_delay" );
350#if L0044_DU_DPB_OUTPUT_DELAY_HRD
351    if(hrd->getSubPicCpbParamsPresentFlag())
352    {
353      WRITE_CODE(sei.m_picDpbOutputDuDelay, hrd->getDpbOutputDelayDuLengthMinus1()+1, "pic_dpb_output_du_delay" );
354    }
355#endif
356    if( hrd->getSubPicCpbParamsPresentFlag() && hrd->getSubPicCpbParamsInPicTimingSEIFlag() )
357    {
358      WRITE_UVLC( sei.m_numDecodingUnitsMinus1,     "num_decoding_units_minus1" );
359      WRITE_FLAG( sei.m_duCommonCpbRemovalDelayFlag, "du_common_cpb_removal_delay_flag" );
360      if( sei.m_duCommonCpbRemovalDelayFlag )
361      {
362        WRITE_CODE( sei.m_duCommonCpbRemovalDelayMinus1, ( hrd->getDuCpbRemovalDelayLengthMinus1() + 1 ),                       "du_common_cpb_removal_delay_minus1" );
363      }
364      for( i = 0; i <= sei.m_numDecodingUnitsMinus1; i ++ )
365      {
366        WRITE_UVLC( sei.m_numNalusInDuMinus1[ i ],  "num_nalus_in_du_minus1");
367        if( ( !sei.m_duCommonCpbRemovalDelayFlag ) && ( i < sei.m_numDecodingUnitsMinus1 ) )
368        {
369          WRITE_CODE( sei.m_duCpbRemovalDelayMinus1[ i ], ( hrd->getDuCpbRemovalDelayLengthMinus1() + 1 ),                        "du_cpb_removal_delay_minus1" );
370        }
371      }
372    }
373#if L0045_CONDITION_SIGNALLING
374  }
375#endif
376  xWriteByteAlign();
377}
378Void SEIWriter::xWriteSEIRecoveryPoint(const SEIRecoveryPoint& sei)
379{
380  WRITE_SVLC( sei.m_recoveryPocCnt,    "recovery_poc_cnt"    );
381  WRITE_FLAG( sei.m_exactMatchingFlag, "exact_matching_flag" );
382  WRITE_FLAG( sei.m_brokenLinkFlag,    "broken_link_flag"    );
383  xWriteByteAlign();
384}
385Void SEIWriter::xWriteSEIFramePacking(const SEIFramePacking& sei)
386{
387  WRITE_UVLC( sei.m_arrangementId,                  "frame_packing_arrangement_id" );
388  WRITE_FLAG( sei.m_arrangementCancelFlag,          "frame_packing_arrangement_cancel_flag" );
389
390  if( sei.m_arrangementCancelFlag == 0 ) {
391    WRITE_CODE( sei.m_arrangementType, 7,           "frame_packing_arrangement_type" );
392
393    WRITE_FLAG( sei.m_quincunxSamplingFlag,         "quincunx_sampling_flag" );
394    WRITE_CODE( sei.m_contentInterpretationType, 6, "content_interpretation_type" );
395    WRITE_FLAG( sei.m_spatialFlippingFlag,          "spatial_flipping_flag" );
396    WRITE_FLAG( sei.m_frame0FlippedFlag,            "frame0_flipped_flag" );
397    WRITE_FLAG( sei.m_fieldViewsFlag,               "field_views_flag" );
398    WRITE_FLAG( sei.m_currentFrameIsFrame0Flag,     "current_frame_is_frame0_flag" );
399
400    WRITE_FLAG( sei.m_frame0SelfContainedFlag,      "frame0_self_contained_flag" );
401    WRITE_FLAG( sei.m_frame1SelfContainedFlag,      "frame1_self_contained_flag" );
402
403    if(sei.m_quincunxSamplingFlag == 0 && sei.m_arrangementType != 5)
404    {
405      WRITE_CODE( sei.m_frame0GridPositionX, 4,     "frame0_grid_position_x" );
406      WRITE_CODE( sei.m_frame0GridPositionY, 4,     "frame0_grid_position_y" );
407      WRITE_CODE( sei.m_frame1GridPositionX, 4,     "frame1_grid_position_x" );
408      WRITE_CODE( sei.m_frame1GridPositionY, 4,     "frame1_grid_position_y" );
409    }
410
411    WRITE_CODE( sei.m_arrangementReservedByte, 8,   "frame_packing_arrangement_reserved_byte" );
412#if L0045_PERSISTENCE_FLAGS
413    WRITE_FLAG( sei.m_arrangementPersistenceFlag,   "frame_packing_arrangement_persistence_flag" );
414#else
415    WRITE_UVLC( sei.m_arrangementRepetetionPeriod,  "frame_packing_arrangement_repetition_period" );
416#endif
417  }
418
419  WRITE_FLAG( sei.m_upsampledAspectRatio,           "upsampled_aspect_ratio" );
420
421  xWriteByteAlign();
422}
423Void SEIWriter::xWriteSEIDisplayOrientation(const SEIDisplayOrientation &sei)
424{
425  WRITE_FLAG( sei.cancelFlag,           "display_orientation_cancel_flag" );
426  if( !sei.cancelFlag )
427  {
428    WRITE_FLAG( sei.horFlip,                   "hor_flip" );
429    WRITE_FLAG( sei.verFlip,                   "ver_flip" );
430    WRITE_CODE( sei.anticlockwiseRotation, 16, "anticlockwise_rotation" );
431#if L0045_PERSISTENCE_FLAGS
432    WRITE_FLAG( sei.persistenceFlag,          "display_orientation_persistence_flag" );
433#else
434    WRITE_UVLC( sei.repetitionPeriod,          "display_orientation_repetition_period" );
435#endif
436#if !REMOVE_SINGLE_SEI_EXTENSION_FLAGS
437    WRITE_FLAG( sei.extensionFlag,             "display_orientation_extension_flag" );
438    assert( !sei.extensionFlag );
439#endif
440  }
441  xWriteByteAlign();
442}
443
444Void SEIWriter::xWriteSEITemporalLevel0Index(const SEITemporalLevel0Index &sei)
445{
446  WRITE_CODE( sei.tl0Idx, 8 , "tl0_idx" );
447  WRITE_CODE( sei.rapIdx, 8 , "rap_idx" );
448  xWriteByteAlign();
449}
450
451Void SEIWriter::xWriteSEIGradualDecodingRefreshInfo(const SEIGradualDecodingRefreshInfo &sei)
452{
453  WRITE_FLAG( sei.m_gdrForegroundFlag, "gdr_foreground_flag");
454  xWriteByteAlign();
455}
456
457Void SEIWriter::xWriteByteAlign()
458{
459  if( m_pcBitIf->getNumberOfWrittenBits() % 8 != 0)
460  {
461    WRITE_FLAG( 1, "bit_equal_to_one" );
462    while( m_pcBitIf->getNumberOfWrittenBits() % 8 != 0 )
463    {
464      WRITE_FLAG( 0, "bit_equal_to_zero" );
465    }
466  }
467};
468
469//! \}
Note: See TracBrowser for help on using the repository browser.