Allow fractional frames per second when computing Time from frames.
[libdcp.git] / asdcplib / src / MXFTypes.cpp
1 /*
2 Copyright (c) 2005-2012, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    MXFTypes.cpp
28     \version $Id: MXFTypes.cpp,v 1.27 2012/02/21 02:09:31 jhurst Exp $
29     \brief   MXF objects
30 */
31
32 #include <KM_prng.h>
33 #include <KM_tai.h>
34 #include "MXFTypes.h"
35 #include <KM_log.h>
36
37 using Kumu::DefaultLogSink;
38
39 //------------------------------------------------------------------------------------------
40 //
41
42 //
43 bool
44 ASDCP::UL::operator==(const UL& rhs) const
45 {
46   if ( m_Value[0] == rhs.m_Value[0] &&
47        m_Value[1] == rhs.m_Value[1] &&
48        m_Value[2] == rhs.m_Value[2] &&
49        m_Value[3] == rhs.m_Value[3] &&
50        m_Value[4] == rhs.m_Value[4] &&
51        m_Value[5] == rhs.m_Value[5] &&
52        m_Value[6] == rhs.m_Value[6] &&
53        //       m_Value[7] == rhs.m_Value[7] &&  // version is ignored when performing lookups
54        m_Value[8] == rhs.m_Value[8] &&
55        m_Value[9] == rhs.m_Value[9] &&
56        m_Value[10] == rhs.m_Value[10] &&
57        m_Value[11] == rhs.m_Value[11] &&
58        m_Value[12] == rhs.m_Value[12] &&
59        m_Value[13] == rhs.m_Value[13] &&
60        m_Value[14] == rhs.m_Value[14] &&
61        m_Value[15] == rhs.m_Value[15]
62        )
63     return true;
64
65   return false;
66 }
67
68 //
69 bool
70 ASDCP::UL::MatchIgnoreStream(const UL& rhs) const
71 {
72   if ( m_Value[0] == rhs.m_Value[0] &&
73        m_Value[1] == rhs.m_Value[1] &&
74        m_Value[2] == rhs.m_Value[2] &&
75        m_Value[3] == rhs.m_Value[3] &&
76        m_Value[4] == rhs.m_Value[4] &&
77        m_Value[5] == rhs.m_Value[5] &&
78        m_Value[6] == rhs.m_Value[6] &&
79        //       m_Value[7] == rhs.m_Value[7] &&  // version is ignored when performing lookups
80        m_Value[8] == rhs.m_Value[8] &&
81        m_Value[9] == rhs.m_Value[9] &&
82        m_Value[10] == rhs.m_Value[10] &&
83        m_Value[11] == rhs.m_Value[11] &&
84        m_Value[12] == rhs.m_Value[12] &&
85        m_Value[13] == rhs.m_Value[13] &&
86        m_Value[14] == rhs.m_Value[14]
87        //       m_Value[15] == rhs.m_Value[15] // ignore stream number
88        )
89     return true;
90
91   return false;
92 }
93
94 //
95 bool
96 ASDCP::UL::ExactMatch(const UL& rhs) const
97 {
98   if ( m_Value[0] == rhs.m_Value[0] &&
99        m_Value[1] == rhs.m_Value[1] &&
100        m_Value[2] == rhs.m_Value[2] &&
101        m_Value[3] == rhs.m_Value[3] &&
102        m_Value[4] == rhs.m_Value[4] &&
103        m_Value[5] == rhs.m_Value[5] &&
104        m_Value[6] == rhs.m_Value[6] &&
105        m_Value[7] == rhs.m_Value[7] &&
106        m_Value[8] == rhs.m_Value[8] &&
107        m_Value[9] == rhs.m_Value[9] &&
108        m_Value[10] == rhs.m_Value[10] &&
109        m_Value[11] == rhs.m_Value[11] &&
110        m_Value[12] == rhs.m_Value[12] &&
111        m_Value[13] == rhs.m_Value[13] &&
112        m_Value[14] == rhs.m_Value[14] &&
113        m_Value[15] == rhs.m_Value[15]
114        )
115     return true;
116
117   return false;
118 }
119
120 const char*
121 ASDCP::UL::EncodeString(char* str_buf, ui32_t buf_len) const
122 {
123   if ( buf_len > 38 ) // room for dotted notation?
124     {
125       snprintf(str_buf, buf_len,
126                "%02x%02x%02x%02x.%02x%02x.%02x%02x.%02x%02x%02x%02x.%02x%02x%02x%02x",
127                m_Value[0],  m_Value[1],  m_Value[2],  m_Value[3],
128                m_Value[4],  m_Value[5],  m_Value[6],  m_Value[7],
129                m_Value[8],  m_Value[9],  m_Value[10], m_Value[11],
130                m_Value[12], m_Value[13], m_Value[14], m_Value[15]
131                );
132
133       return str_buf;
134     }
135   else if ( buf_len > 32 ) // room for compact?
136     {
137       snprintf(str_buf, buf_len,
138                "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
139                m_Value[0],  m_Value[1],  m_Value[2],  m_Value[3],
140                m_Value[4],  m_Value[5],  m_Value[6],  m_Value[7],
141                m_Value[8],  m_Value[9],  m_Value[10], m_Value[11],
142                m_Value[12], m_Value[13], m_Value[14], m_Value[15]
143                );
144
145       return str_buf;
146     }
147
148   return 0;
149 }
150
151 //
152 void
153 ASDCP::UMID::MakeUMID(int Type)
154 {
155   UUID AssetID;
156   Kumu::GenRandomValue(AssetID);
157   MakeUMID(Type, AssetID);
158 }
159
160 //
161 void
162 ASDCP::UMID::MakeUMID(int Type, const UUID& AssetID)
163 {
164   // Set the non-varying base of the UMID
165   static const byte_t UMIDBase[10] = { 0x06, 0x0a, 0x2b, 0x34, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };
166   memcpy(m_Value, UMIDBase, 10);
167   m_Value[10] = Type;  // Material Type
168   m_Value[12] = 0x13;  // length
169
170   // preserved for compatibility with mfxlib
171   if( Type > 4 ) m_Value[7] = 5;
172   m_Value[11] = 0x20; // UUID/UL method, number gen undefined
173
174   // Instance Number
175   m_Value[13] = m_Value[14] = m_Value[15] = 0;
176   
177   memcpy(&m_Value[16], AssetID.Value(), AssetID.Size());
178   m_HasValue = true;
179 }
180
181
182 // Write the UMID value to the given buffer in the form
183 //   [00000000.0000.0000.00000000],00,00,00,00,00000000.0000.0000.00000000.00000000]
184 // or
185 //   [00000000.0000.0000.00000000],00,00,00,00,00000000-0000-0000-0000-000000000000]
186 // returns 0 if the buffer is smaller than DateTimeLen
187 const char*
188 ASDCP::UMID::EncodeString(char* str_buf, ui32_t buf_len) const
189 {
190   assert(str_buf);
191
192   snprintf(str_buf, buf_len, "[%02x%02x%02x%02x.%02x%02x.%02x%02x.%02x%02x%02x%02x],%02x,%02x,%02x,%02x,",
193            m_Value[0],  m_Value[1],  m_Value[2],  m_Value[3],
194            m_Value[4],  m_Value[5],  m_Value[6],  m_Value[7],
195            m_Value[8],  m_Value[9],  m_Value[10], m_Value[11],
196            m_Value[12], m_Value[13], m_Value[14], m_Value[15]
197            );
198
199   ui32_t offset = strlen(str_buf);
200
201   if ( ( m_Value[8] & 0x80 ) == 0 )
202     {
203       // half-swapped UL, use [bbaa9988.ddcc.ffee.00010203.04050607]
204       snprintf(str_buf + offset, buf_len - offset,
205                "[%02x%02x%02x%02x.%02x%02x.%02x%02x.%02x%02x%02x%02x.%02x%02x%02x%02x]",
206                m_Value[24], m_Value[25], m_Value[26], m_Value[27],
207                m_Value[28], m_Value[29], m_Value[30], m_Value[31],
208                m_Value[16], m_Value[17], m_Value[18], m_Value[19],
209                m_Value[20], m_Value[21], m_Value[22], m_Value[23]
210                );
211     }
212   else
213     {
214       // UUID, use {00112233-4455-6677-8899-aabbccddeeff}
215       snprintf(str_buf + offset, buf_len - offset,
216                "{%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
217                m_Value[16], m_Value[17], m_Value[18], m_Value[19],
218                m_Value[20], m_Value[21], m_Value[22], m_Value[23],
219                m_Value[24], m_Value[25], m_Value[26], m_Value[27],
220                m_Value[28], m_Value[29], m_Value[30], m_Value[31]
221                );
222     }
223
224   return str_buf;
225 }
226
227 //------------------------------------------------------------------------------------------
228 //
229
230 //
231 const ASDCP::MXF::UTF16String&
232 ASDCP::MXF::UTF16String::operator=(const char* sz)
233 {
234   if ( sz == 0 || *sz == 0 )
235     erase();
236
237   else
238     this->assign(sz);
239   
240   return *this;
241 }
242
243 //
244 const ASDCP::MXF::UTF16String&
245 ASDCP::MXF::UTF16String::operator=(const std::string& str)
246 {
247   this->assign(str);
248   return *this;
249 }
250
251 //
252 const char*
253 ASDCP::MXF::UTF16String::EncodeString(char* str_buf, ui32_t buf_len) const
254 {
255   ui32_t write_len = Kumu::xmin(buf_len - 1, (ui32_t)size());
256   strncpy(str_buf, c_str(), write_len);
257   str_buf[write_len] = 0;
258   return str_buf;
259 }
260
261 //
262 bool
263 ASDCP::MXF::UTF16String::Unarchive(Kumu::MemIOReader* Reader)
264 {
265   erase();
266   const ui16_t* p = (ui16_t*)Reader->CurrentData();
267   ui32_t length = Reader->Remainder() / 2;
268   char mb_buf[MB_LEN_MAX+1];
269
270   for ( ui32_t i = 0; i < length; i++ )
271     {
272       int count = wctomb(mb_buf, KM_i16_BE(p[i]));
273
274       if ( count == -1 )
275         {
276           DefaultLogSink().Error("Unable to decode wide character 0x%04hx\n", p[i]);
277           return false;
278         }
279
280       assert(count <= MB_LEN_MAX);
281       mb_buf[count] = 0;
282       this->append(mb_buf);
283     }
284
285   Reader->SkipOffset(length*2);
286   return true;
287 }
288
289 //
290 bool
291 ASDCP::MXF::UTF16String::Archive(Kumu::MemIOWriter* Writer) const
292 {
293   if ( size() > IdentBufferLen )
294     {
295       DefaultLogSink().Error("String length exceeds maximum %u bytes\n", IdentBufferLen);
296       return false;
297     }
298
299   const char* mbp = c_str();
300   wchar_t wcp;
301   ui32_t remainder = size();
302   ui32_t length = size();
303   ui32_t i = 0;
304
305   while ( i < length )
306     {
307       int count = mbtowc(&wcp, mbp+i, remainder);
308
309       if ( count == -1 )
310         {
311           DefaultLogSink().Error("Error decoding multi-byte sequence starting at offset %u\n", i);
312           return false;
313         }
314       else if ( count  == 0 )
315         break;
316
317       bool result = Writer->WriteUi16BE((ui16_t)wcp);
318
319       if ( result == false )
320         {
321           DefaultLogSink().Error("No more space in memory IO writer\n");
322           return false;
323         }
324
325       i += count;
326       remainder -= count;
327     }
328
329   return true;
330 }
331
332 //------------------------------------------------------------------------------------------
333 //
334
335 //
336 const ASDCP::MXF::ISO8String&
337 ASDCP::MXF::ISO8String::operator=(const char* sz)
338 {
339   if ( sz == 0 || *sz == 0 )
340     erase();
341
342   else
343     this->assign(sz);
344   
345   return *this;
346 }
347
348 //
349 const ASDCP::MXF::ISO8String&
350 ASDCP::MXF::ISO8String::operator=(const std::string& str)
351 {
352   this->assign(str);
353   return *this;
354 }
355
356 //
357 const char*
358 ASDCP::MXF::ISO8String::EncodeString(char* str_buf, ui32_t buf_len) const
359 {
360   ui32_t write_len = Kumu::xmin(buf_len - 1, (ui32_t)size());
361   strncpy(str_buf, c_str(), write_len);
362   str_buf[write_len] = 0;
363   return str_buf;
364 }
365
366 //
367 bool
368 ASDCP::MXF::ISO8String::Unarchive(Kumu::MemIOReader* Reader)
369 {
370   assign((char*)Reader->CurrentData(), Reader->Remainder());
371   return true;
372 }
373
374 //
375 bool
376 ASDCP::MXF::ISO8String::Archive(Kumu::MemIOWriter* Writer) const
377 {
378   if ( size() > IdentBufferLen )
379     {
380       DefaultLogSink().Error("String length exceeds maximum %u bytes\n", IdentBufferLen);
381       return false;
382     }
383
384   return Writer->WriteString(*this);
385 }
386
387 //------------------------------------------------------------------------------------------
388 //
389
390 ASDCP::MXF::TLVReader::TLVReader(const byte_t* p, ui32_t c, IPrimerLookup* PrimerLookup) :
391   MemIOReader(p, c), m_Lookup(PrimerLookup)
392 {
393   Result_t result = RESULT_OK;
394
395   while ( Remainder() > 0 && ASDCP_SUCCESS(result) )
396     {
397       TagValue Tag;
398       ui16_t pkt_len = 0;
399
400       if ( MemIOReader::ReadUi8(&Tag.a) )
401         if ( MemIOReader::ReadUi8(&Tag.b) )
402           if ( MemIOReader::ReadUi16BE(&pkt_len) )
403             {
404               m_ElementMap.insert(TagMap::value_type(Tag, ItemInfo(m_size, pkt_len)));
405               if ( SkipOffset(pkt_len) )
406                 continue;;
407             }
408
409       DefaultLogSink().Error("Malformed Set\n");
410       m_ElementMap.clear();
411       result = RESULT_KLV_CODING;
412     }
413 }
414
415 //
416 bool
417 ASDCP::MXF::TLVReader::FindTL(const MDDEntry& Entry)
418 {
419   if ( m_Lookup == 0 )
420     {
421       DefaultLogSink().Error("No Lookup service\n");
422       return false;
423     }
424   
425   TagValue TmpTag;
426
427   if ( m_Lookup->TagForKey(Entry.ul, TmpTag) != RESULT_OK )
428     {
429       if ( Entry.tag.a == 0 )
430         {
431           //      DefaultLogSink().Debug("No such UL in this TL list: %s (%02x %02x)\n",
432           //                             Entry.name, Entry.tag.a, Entry.tag.b);
433           return false;
434         }
435
436       TmpTag = Entry.tag;
437     }
438
439   TagMap::iterator e_i = m_ElementMap.find(TmpTag);
440
441   if ( e_i != m_ElementMap.end() )
442     {
443       m_size = (*e_i).second.first;
444       m_capacity = m_size + (*e_i).second.second;
445       return true;
446     }
447
448   //  DefaultLogSink().Debug("Not Found (%02x %02x): %s\n", TmpTag.a, TmpTag.b, Entry.name);
449   return false;
450 }
451
452 //
453 ASDCP::Result_t
454 ASDCP::MXF::TLVReader::ReadObject(const MDDEntry& Entry, Kumu::IArchive* Object)
455 {
456   ASDCP_TEST_NULL(Object);
457
458   if ( FindTL(Entry) )
459     {
460       if ( m_size < m_capacity ) // don't try to unarchive an empty item
461         return Object->Unarchive(this) ? RESULT_OK : RESULT_KLV_CODING;
462     }
463
464   return RESULT_FALSE;
465 }
466
467 //
468 ASDCP::Result_t
469 ASDCP::MXF::TLVReader::ReadUi8(const MDDEntry& Entry, ui8_t* value)
470 {
471   ASDCP_TEST_NULL(value);
472
473   if ( FindTL(Entry) )
474     return MemIOReader::ReadUi8(value) ? RESULT_OK : RESULT_KLV_CODING;
475
476   return RESULT_FALSE;
477 }
478
479 //
480 ASDCP::Result_t
481 ASDCP::MXF::TLVReader::ReadUi16(const MDDEntry& Entry, ui16_t* value)
482 {
483   ASDCP_TEST_NULL(value);
484
485   if ( FindTL(Entry) )
486     return MemIOReader::ReadUi16BE(value) ? RESULT_OK : RESULT_KLV_CODING;
487
488   return RESULT_FALSE;
489 }
490
491 //
492 ASDCP::Result_t
493 ASDCP::MXF::TLVReader::ReadUi32(const MDDEntry& Entry, ui32_t* value)
494 {
495   ASDCP_TEST_NULL(value);
496
497   if ( FindTL(Entry) )
498     return MemIOReader::ReadUi32BE(value) ? RESULT_OK : RESULT_KLV_CODING;
499
500   return RESULT_FALSE;
501 }
502
503 //
504 ASDCP::Result_t
505 ASDCP::MXF::TLVReader::ReadUi64(const MDDEntry& Entry, ui64_t* value)
506 {
507   ASDCP_TEST_NULL(value);
508
509   if ( FindTL(Entry) )
510     return MemIOReader::ReadUi64BE(value) ? RESULT_OK : RESULT_KLV_CODING;
511
512   return RESULT_FALSE;
513 }
514
515 //------------------------------------------------------------------------------------------
516 //
517
518 ASDCP::MXF::TLVWriter::TLVWriter(byte_t* p, ui32_t c, IPrimerLookup* PrimerLookup) :
519   MemIOWriter(p, c), m_Lookup(PrimerLookup)
520 {
521   assert(c > 3);
522 }
523
524 //
525 ASDCP::Result_t
526 ASDCP::MXF::TLVWriter::WriteTag(const MDDEntry& Entry)
527 {
528   if ( m_Lookup == 0 )
529     {
530       DefaultLogSink().Error("No Primer object available\n");
531       return RESULT_FAIL;
532     }
533
534   TagValue TmpTag;
535
536   if ( m_Lookup->InsertTag(Entry, TmpTag) != RESULT_OK )
537     {
538       DefaultLogSink().Error("No tag for entry %s\n", Entry.name);
539       return RESULT_FAIL;
540     }
541
542   if ( ! MemIOWriter::WriteUi8(TmpTag.a) ) return RESULT_KLV_CODING;
543   if ( ! MemIOWriter::WriteUi8(TmpTag.b) ) return RESULT_KLV_CODING;
544   return RESULT_OK;
545 }
546
547 //
548 ASDCP::Result_t
549 ASDCP::MXF::TLVWriter::WriteObject(const MDDEntry& Entry, Kumu::IArchive* Object)
550 {
551   ASDCP_TEST_NULL(Object);
552
553   if ( Entry.optional && ! Object->HasValue() )
554     return RESULT_OK;
555
556   Result_t result = WriteTag(Entry);
557
558   if ( ASDCP_SUCCESS(result) )
559     {
560       // write a temp length
561       byte_t* l_p = CurrentData();
562
563       if ( ! MemIOWriter::WriteUi16BE(0) ) return RESULT_KLV_CODING;
564
565       ui32_t before = Length();
566       if ( ! Object->Archive(this) ) return RESULT_KLV_CODING;
567       if ( (Length() - before) > 0xffffL ) return RESULT_KLV_CODING;
568       Kumu::i2p<ui16_t>(KM_i16_BE(Length() - before), l_p);
569     }
570
571   return result;
572 }
573
574 //
575 ASDCP::Result_t
576 ASDCP::MXF::TLVWriter::WriteUi8(const MDDEntry& Entry, ui8_t* value)
577 {
578   ASDCP_TEST_NULL(value);
579   Result_t result = WriteTag(Entry);
580
581   if ( ASDCP_SUCCESS(result) )
582     {
583       if ( ! MemIOWriter::WriteUi16BE(sizeof(ui8_t)) ) return RESULT_KLV_CODING;
584       if ( ! MemIOWriter::WriteUi8(*value) ) return RESULT_KLV_CODING;
585     }
586   
587   return result;
588 }
589
590 //
591 ASDCP::Result_t
592 ASDCP::MXF::TLVWriter::WriteUi16(const MDDEntry& Entry, ui16_t* value)
593 {
594   ASDCP_TEST_NULL(value);
595   Result_t result = WriteTag(Entry);
596
597   if ( KM_SUCCESS(result) )
598     {
599       if ( ! MemIOWriter::WriteUi16BE(sizeof(ui16_t)) ) return RESULT_KLV_CODING;
600       if ( ! MemIOWriter::WriteUi16BE(*value) ) return RESULT_KLV_CODING;
601     }
602
603   return result;
604 }
605
606 //
607 ASDCP::Result_t
608 ASDCP::MXF::TLVWriter::WriteUi32(const MDDEntry& Entry, ui32_t* value)
609 {
610   ASDCP_TEST_NULL(value);
611   Result_t result = WriteTag(Entry);
612
613   if ( KM_SUCCESS(result) )
614     {
615       if ( ! MemIOWriter::WriteUi16BE(sizeof(ui32_t)) ) return RESULT_KLV_CODING;
616       if ( ! MemIOWriter::WriteUi32BE(*value) ) return RESULT_KLV_CODING;
617     }
618
619   return result;
620 }
621
622 //
623 ASDCP::Result_t
624 ASDCP::MXF::TLVWriter::WriteUi64(const MDDEntry& Entry, ui64_t* value)
625 {
626   ASDCP_TEST_NULL(value);
627   Result_t result = WriteTag(Entry);
628
629   if ( KM_SUCCESS(result) )
630     {
631       if ( ! MemIOWriter::WriteUi16BE(sizeof(ui64_t)) ) return RESULT_KLV_CODING;
632       if ( ! MemIOWriter::WriteUi64BE(*value) ) return RESULT_KLV_CODING;
633     }
634
635   return result;
636 }
637
638
639 //----------------------------------------------------------------------------------------------------
640 //
641
642 ASDCP::MXF::Raw::Raw()
643 {
644   Capacity(256);
645 }
646
647 ASDCP::MXF::Raw::~Raw()
648 {
649 }
650
651 //
652 bool
653 ASDCP::MXF::Raw::Unarchive(Kumu::MemIOReader* Reader)
654 {
655   ui32_t payload_size = Reader->Remainder();
656   if ( payload_size == 0 ) return false;
657   if ( KM_FAILURE(Capacity(payload_size)) ) return false;
658
659   memcpy(Data(), Reader->CurrentData(), payload_size);
660   Length(payload_size);
661   return true;
662 }
663
664 //
665 bool
666 ASDCP::MXF::Raw::Archive(Kumu::MemIOWriter* Writer) const
667 {
668   return Writer->WriteRaw(RoData(), Length());
669 }
670
671 //
672 const char*
673 ASDCP::MXF::Raw::EncodeString(char* str_buf, ui32_t buf_len) const
674 {
675   *str_buf = 0;
676   Kumu::bin2hex(RoData(), Length(), str_buf, buf_len);
677   return str_buf;
678 }
679
680 //
681 // end MXFTypes.cpp
682 //