1 /*
2 * The contents of this file are subject to the Mozilla Public
3 * License Version 1.1 (the "License"); you may not use this file
4 * except in compliance with the License. You may obtain a copy of
5 * the License at http://www.mozilla.org/MPL/
6 *
7 * Software distributed under the License is distributed on an "AS
8 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9 * implied. See the License for the specific language governing
10 * rights and limitations under the License.
11 *
12 * The Original Code is MPEG4IP.
13 *
14 * The Initial Developer of the Original Code is Cisco Systems Inc.
15 * Portions created by Cisco Systems Inc. are
16 * Copyright (C) Cisco Systems Inc. 2001. All Rights Reserved.
17 *
18 * Contributor(s):
19 * Dave Mackie dmackie@cisco.com
20 */
21
22 #include "mp4common.h"
23
MP4BaseDescriptor(u_int8_t tag)24 MP4BaseDescriptor::MP4BaseDescriptor (u_int8_t tag) : MP4Descriptor(tag)
25 {
26 switch (tag) {
27 case MP4ESIDIncDescrTag:
28 AddProperty( /* 0 */
29 new MP4Integer32Property("id"));
30 break;
31 case MP4ESIDRefDescrTag:
32 AddProperty( /* 0 */
33 new MP4Integer16Property("refIndex"));
34 break;
35 case MP4IPIPtrDescrTag:
36 AddProperty( /* 0 */
37 new MP4Integer16Property("IPIESId"));
38 break;
39 case MP4SupplContentIdDescrTag:
40 AddProperty( /* 0 */
41 new MP4BytesProperty("languageCode", 3));
42 AddProperty( /* 1 */
43 new MP4StringProperty("title", Counted));
44 AddProperty( /* 2 */
45 new MP4StringProperty("value", Counted));
46 break;
47 case MP4IPMPPtrDescrTag:
48 AddProperty( /* 0 */
49 new MP4Integer8Property("IPMPDescriptorId"));
50 break;
51 case MP4ExtProfileLevelDescrTag:
52 AddProperty( /* 0 */
53 new MP4Integer8Property("profileLevelIndicationIndex"));
54 AddProperty( /* 1 */
55 new MP4Integer8Property("ODProfileLevelIndication"));
56 AddProperty( /* 2 */
57 new MP4Integer8Property("sceneProfileLevelIndication"));
58 AddProperty( /* 3 */
59 new MP4Integer8Property("audioProfileLevelIndication"));
60 AddProperty( /* 4 */
61 new MP4Integer8Property("visualProfileLevelIndication"));
62 AddProperty( /* 5 */
63 new MP4Integer8Property("graphicsProfileLevelIndication"));
64 AddProperty( /* 6 */
65 new MP4Integer8Property("MPEGJProfileLevelIndication"));
66 break;
67 default:
68 MP4Printf("error in base descriptor - tag %u", tag);
69 break;
70
71 }
72 }
73
MP4BytesDescriptor(u_int8_t tag)74 MP4BytesDescriptor::MP4BytesDescriptor (u_int8_t tag) : MP4Descriptor(tag)
75 {
76 m_size_offset = 0;
77 m_bytes_index = 0;
78 if (tag >= MP4ExtDescrTagsStart && tag <= MP4ExtDescrTagsEnd) {
79 AddProperty( /* 0 */
80 new MP4BytesProperty("data"));
81 } else {
82 switch (tag) {
83 case MP4DecSpecificDescrTag:
84 AddProperty( /* 0 */
85 new MP4BytesProperty("info"));
86 // no change to m_size
87 break;
88 case MP4IPMPDescrTag:
89 AddProperty( /* 0 */
90 new MP4Integer8Property("IPMPDescriptorId"));
91 AddProperty( /* 1 */
92 new MP4Integer16Property("IPMPSType"));
93 AddProperty( /* 2 */
94 new MP4BytesProperty("IPMPData"));
95 /* note: if IPMPSType == 0, IPMPData is an URL */
96 m_size_offset = 3;
97 m_bytes_index = 2;
98 break;
99 case MP4RegistrationDescrTag:
100 AddProperty( /* 0 */
101 new MP4Integer32Property("formatIdentifier"));
102 AddProperty( /* 1 */
103 new MP4BytesProperty("additionalIdentificationInfo"));
104 m_size_offset = 4;
105 m_bytes_index = 1;
106 break;
107 default:
108 MP4Printf("error in bytes descriptor - tag %u", tag);
109 break;
110 }
111 }
112 }
113
Read(MP4File * pFile)114 void MP4BytesDescriptor::Read(MP4File *pFile)
115 {
116 ReadHeader(pFile);
117
118 /* byte properties need to know how long they are before reading */
119 ((MP4BytesProperty*)m_pProperties[m_bytes_index])->SetValueSize(m_size - m_size_offset);
120
121 ReadProperties(pFile);
122 }
MP4IODescriptor()123 MP4IODescriptor::MP4IODescriptor()
124 : MP4Descriptor(MP4FileIODescrTag)
125 {
126 /* N.B. other member functions depend on the property indicies */
127 AddProperty( /* 0 */
128 new MP4BitfieldProperty("objectDescriptorId", 10));
129 AddProperty( /* 1 */
130 new MP4BitfieldProperty("URLFlag", 1));
131 AddProperty( /* 2 */
132 new MP4BitfieldProperty("includeInlineProfileLevelFlag", 1));
133 AddProperty( /* 3 */
134 new MP4BitfieldProperty("reserved", 4));
135 AddProperty( /* 4 */
136 new MP4StringProperty("URL", Counted));
137 AddProperty( /* 5 */
138 new MP4Integer8Property("ODProfileLevelId"));
139 AddProperty( /* 6 */
140 new MP4Integer8Property("sceneProfileLevelId"));
141 AddProperty( /* 7 */
142 new MP4Integer8Property("audioProfileLevelId"));
143 AddProperty( /* 8 */
144 new MP4Integer8Property("visualProfileLevelId"));
145 AddProperty( /* 9 */
146 new MP4Integer8Property("graphicsProfileLevelId"));
147 AddProperty( /* 10 */
148 new MP4DescriptorProperty("esIds",
149 MP4ESIDIncDescrTag, 0, Required, Many));
150 AddProperty( /* 11 */
151 new MP4DescriptorProperty("ociDescr",
152 MP4OCIDescrTagsStart, MP4OCIDescrTagsEnd, Optional, Many));
153 AddProperty( /* 12 */
154 new MP4DescriptorProperty("ipmpDescrPtr",
155 MP4IPMPPtrDescrTag, 0, Optional, Many));
156 AddProperty( /* 13 */
157 new MP4DescriptorProperty("extDescr",
158 MP4ExtDescrTagsStart, MP4ExtDescrTagsEnd, Optional, Many));
159
160 SetReadMutate(2);
161 }
162
Generate()163 void MP4IODescriptor::Generate()
164 {
165 ((MP4BitfieldProperty*)m_pProperties[0])->SetValue(1);
166 ((MP4BitfieldProperty*)m_pProperties[3])->SetValue(0xF);
167 for (u_int32_t i = 5; i <= 9; i++) {
168 ((MP4Integer8Property*)m_pProperties[i])->SetValue(0xFF);
169 }
170 }
171
Mutate()172 void MP4IODescriptor::Mutate()
173 {
174 bool urlFlag = ((MP4BitfieldProperty*)m_pProperties[1])->GetValue();
175
176 m_pProperties[4]->SetImplicit(!urlFlag);
177 for (u_int32_t i = 5; i <= 12; i++) {
178 m_pProperties[i]->SetImplicit(urlFlag);
179 }
180 }
181
MP4ODescriptor()182 MP4ODescriptor::MP4ODescriptor()
183 : MP4Descriptor(MP4FileODescrTag)
184 {
185 /* N.B. other member functions depend on the property indicies */
186 AddProperty( /* 0 */
187 new MP4BitfieldProperty("objectDescriptorId", 10));
188 AddProperty( /* 1 */
189 new MP4BitfieldProperty("URLFlag", 1));
190 AddProperty( /* 2 */
191 new MP4BitfieldProperty("reserved", 5));
192 AddProperty( /* 3 */
193 new MP4StringProperty("URL", Counted));
194 AddProperty( /* 4 */
195 new MP4DescriptorProperty("esIds",
196 MP4ESIDRefDescrTag, 0, Required, Many));
197 AddProperty( /* 5 */
198 new MP4DescriptorProperty("ociDescr",
199 MP4OCIDescrTagsStart, MP4OCIDescrTagsEnd, Optional, Many));
200 AddProperty( /* 6 */
201 new MP4DescriptorProperty("ipmpDescrPtr",
202 MP4IPMPPtrDescrTag, 0, Optional, Many));
203 AddProperty( /* 7 */
204 new MP4DescriptorProperty("extDescr",
205 MP4ExtDescrTagsStart, MP4ExtDescrTagsEnd, Optional, Many));
206
207 SetReadMutate(2);
208 }
209
Generate()210 void MP4ODescriptor::Generate()
211 {
212 ((MP4BitfieldProperty*)m_pProperties[2])->SetValue(0x1F);
213 }
214
Mutate()215 void MP4ODescriptor::Mutate()
216 {
217 bool urlFlag = ((MP4BitfieldProperty*)m_pProperties[1])->GetValue();
218
219 m_pProperties[3]->SetImplicit(!urlFlag);
220 for (u_int32_t i = 4; i <= 6; i++) {
221 m_pProperties[i]->SetImplicit(urlFlag);
222 }
223 }
224
MP4ESDescriptor()225 MP4ESDescriptor::MP4ESDescriptor()
226 : MP4Descriptor(MP4ESDescrTag)
227 {
228 /* N.B. other class functions depend on the property indicies */
229 AddProperty( /* 0 */
230 new MP4Integer16Property("ESID"));
231 AddProperty( /* 1 */
232 new MP4BitfieldProperty("streamDependenceFlag", 1));
233 AddProperty( /* 2 */
234 new MP4BitfieldProperty("URLFlag", 1));
235 AddProperty( /* 3 */
236 new MP4BitfieldProperty("OCRstreamFlag", 1));
237 AddProperty( /* 4 */
238 new MP4BitfieldProperty("streamPriority", 5));
239 AddProperty( /* 5 */
240 new MP4Integer16Property("dependsOnESID"));
241 AddProperty( /* 6 */
242 new MP4StringProperty("URL", Counted));
243 AddProperty( /* 7 */
244 new MP4Integer16Property("OCRESID"));
245 AddProperty( /* 8 */
246 new MP4DescriptorProperty("decConfigDescr",
247 MP4DecConfigDescrTag, 0, Required, OnlyOne));
248 AddProperty( /* 9 */
249 new MP4DescriptorProperty("slConfigDescr",
250 MP4SLConfigDescrTag, 0, Required, OnlyOne));
251 AddProperty( /* 10 */
252 new MP4DescriptorProperty("ipiPtr",
253 MP4IPIPtrDescrTag, 0, Optional, OnlyOne));
254 AddProperty( /* 11 */
255 new MP4DescriptorProperty("ipIds",
256 MP4ContentIdDescrTag, MP4SupplContentIdDescrTag, Optional, Many));
257 AddProperty( /* 12 */
258 new MP4DescriptorProperty("ipmpDescrPtr",
259 MP4IPMPPtrDescrTag, 0, Optional, Many));
260 AddProperty( /* 13 */
261 new MP4DescriptorProperty("langDescr",
262 MP4LanguageDescrTag, 0, Optional, Many));
263 AddProperty( /* 14 */
264 new MP4DescriptorProperty("qosDescr",
265 MP4QosDescrTag, 0, Optional, OnlyOne));
266 AddProperty( /* 15 */
267 new MP4DescriptorProperty("regDescr",
268 MP4RegistrationDescrTag, 0, Optional, OnlyOne));
269 AddProperty( /* 16 */
270 new MP4DescriptorProperty("extDescr",
271 MP4ExtDescrTagsStart, MP4ExtDescrTagsEnd, Optional, Many));
272
273 SetReadMutate(5);
274 }
275
Mutate()276 void MP4ESDescriptor::Mutate()
277 {
278 bool streamDependFlag =
279 ((MP4BitfieldProperty*)m_pProperties[1])->GetValue();
280 m_pProperties[5]->SetImplicit(!streamDependFlag);
281
282 bool urlFlag =
283 ((MP4BitfieldProperty*)m_pProperties[2])->GetValue();
284 m_pProperties[6]->SetImplicit(!urlFlag);
285
286 bool ocrFlag =
287 ((MP4BitfieldProperty*)m_pProperties[3])->GetValue();
288 m_pProperties[7]->SetImplicit(!ocrFlag);
289 }
290
MP4DecConfigDescriptor()291 MP4DecConfigDescriptor::MP4DecConfigDescriptor()
292 : MP4Descriptor(MP4DecConfigDescrTag)
293 {
294 AddProperty( /* 0 */
295 new MP4Integer8Property("objectTypeId"));
296 AddProperty( /* 1 */
297 new MP4BitfieldProperty("streamType", 6));
298 AddProperty( /* 2 */
299 new MP4BitfieldProperty("upStream", 1));
300 AddProperty( /* 3 */
301 new MP4BitfieldProperty("reserved", 1));
302 AddProperty( /* 4 */
303 new MP4BitfieldProperty("bufferSizeDB", 24));
304 AddProperty( /* 5 */
305 new MP4Integer32Property("maxBitrate"));
306 AddProperty( /* 6 */
307 new MP4Integer32Property("avgBitrate"));
308 AddProperty( /* 7 */
309 new MP4DescriptorProperty("decSpecificInfo",
310 MP4DecSpecificDescrTag, 0, Optional, OnlyOne));
311 AddProperty( /* 8 */
312 new MP4DescriptorProperty("profileLevelIndicationIndexDescr",
313 MP4ExtProfileLevelDescrTag, 0, Optional, Many));
314 }
315
Generate()316 void MP4DecConfigDescriptor::Generate()
317 {
318 ((MP4BitfieldProperty*)m_pProperties[3])->SetValue(1);
319 }
320
MP4SLConfigDescriptor()321 MP4SLConfigDescriptor::MP4SLConfigDescriptor()
322 : MP4Descriptor(MP4SLConfigDescrTag)
323 {
324 AddProperty( /* 0 */
325 new MP4Integer8Property("predefined"));
326 AddProperty( /* 1 */
327 new MP4BitfieldProperty("useAccessUnitStartFlag", 1));
328 AddProperty( /* 2 */
329 new MP4BitfieldProperty("useAccessUnitEndFlag", 1));
330 AddProperty( /* 3 */
331 new MP4BitfieldProperty("useRandomAccessPointFlag", 1));
332 AddProperty( /* 4 */
333 new MP4BitfieldProperty("hasRandomAccessUnitsOnlyFlag", 1));
334 AddProperty( /* 5 */
335 new MP4BitfieldProperty("usePaddingFlag", 1));
336 AddProperty( /* 6 */
337 new MP4BitfieldProperty("useTimeStampsFlag", 1));
338 AddProperty( /* 7 */
339 new MP4BitfieldProperty("useIdleFlag", 1));
340 AddProperty( /* 8 */
341 new MP4BitfieldProperty("durationFlag", 1));
342 AddProperty( /* 9 */
343 new MP4Integer32Property("timeStampResolution"));
344 AddProperty( /* 10 */
345 new MP4Integer32Property("OCRResolution"));
346 AddProperty( /* 11 */
347 new MP4Integer8Property("timeStampLength"));
348 AddProperty( /* 12 */
349 new MP4Integer8Property("OCRLength"));
350 AddProperty( /* 13 */
351 new MP4Integer8Property("AULength"));
352 AddProperty( /* 14 */
353 new MP4Integer8Property("instantBitrateLength"));
354 AddProperty( /* 15 */
355 new MP4BitfieldProperty("degradationPriortyLength", 4));
356 AddProperty( /* 16 */
357 new MP4BitfieldProperty("AUSeqNumLength", 5));
358 AddProperty( /* 17 */
359 new MP4BitfieldProperty("packetSeqNumLength", 5));
360 AddProperty( /* 18 */
361 new MP4BitfieldProperty("reserved", 2));
362
363 // if durationFlag
364 AddProperty( /* 19 */
365 new MP4Integer32Property("timeScale"));
366 AddProperty( /* 20 */
367 new MP4Integer16Property("accessUnitDuration"));
368 AddProperty( /* 21 */
369 new MP4Integer16Property("compositionUnitDuration"));
370
371 // if !useTimeStampsFlag
372 AddProperty( /* 22 */
373 new MP4BitfieldProperty("startDecodingTimeStamp", 64));
374 AddProperty( /* 23 */
375 new MP4BitfieldProperty("startCompositionTimeStamp", 64));
376 }
377
Generate()378 void MP4SLConfigDescriptor::Generate()
379 {
380 // by default all tracks in an mp4 file
381 // use predefined SLConfig descriptor == 2
382 ((MP4Integer8Property*)m_pProperties[0])->SetValue(2);
383
384 // which implies UseTimestampsFlag = 1
385 ((MP4BitfieldProperty*)m_pProperties[6])->SetValue(1);
386
387 // reserved = 3
388 ((MP4BitfieldProperty*)m_pProperties[18])->SetValue(3);
389 }
390
Read(MP4File * pFile)391 void MP4SLConfigDescriptor::Read(MP4File* pFile)
392 {
393 ReadHeader(pFile);
394
395 // read the first property, 'predefined'
396 ReadProperties(pFile, 0, 1);
397
398 // if predefined == 0
399 if (((MP4Integer8Property*)m_pProperties[0])->GetValue() == 0) {
400
401 /* read the next 18 properties */
402 ReadProperties(pFile, 1, 18);
403 }
404
405 // now mutate
406 Mutate();
407
408 // and read the remaining properties
409 ReadProperties(pFile, 19);
410 }
411
Mutate()412 void MP4SLConfigDescriptor::Mutate()
413 {
414 u_int32_t i;
415 u_int8_t predefined =
416 ((MP4Integer8Property*)m_pProperties[0])->GetValue();
417
418 if (predefined) {
419 // properties 1-18 are implicit
420 for (i = 1; i < m_pProperties.Size(); i++) {
421 m_pProperties[i]->SetImplicit(true);
422 }
423
424 if (predefined == 1) {
425 // UseTimestampsFlag = 0
426 ((MP4BitfieldProperty*)m_pProperties[6])->SetValue(0);
427
428 // TimestampResolution = 1000
429 ((MP4Integer32Property*)m_pProperties[9])->SetValue(1000);
430
431 // TimeStampLength = 32
432 ((MP4Integer8Property*)m_pProperties[11])->SetValue(32);
433
434 } else if (predefined == 2) {
435 // UseTimestampsFlag = 1
436 ((MP4BitfieldProperty*)m_pProperties[6])->SetValue(1);
437 }
438 } else {
439 #if 1
440 for (i = 1; i <= 18; i++) {
441 m_pProperties[i]->SetImplicit(false);
442 }
443 ((MP4BitfieldProperty*)m_pProperties[18])->SetValue(3);
444 #endif
445 }
446
447 bool durationFlag =
448 ((MP4BitfieldProperty*)m_pProperties[8])->GetValue();
449
450 for (i = 19; i <= 21; i++) {
451 m_pProperties[i]->SetImplicit(!durationFlag);
452 }
453
454 bool useTimeStampsFlag =
455 ((MP4BitfieldProperty*)m_pProperties[6])->GetValue();
456
457 for (i = 22; i <= 23; i++) {
458 m_pProperties[i]->SetImplicit(useTimeStampsFlag);
459
460 u_int8_t timeStampLength = MIN(64,
461 ((MP4Integer8Property*)m_pProperties[11])->GetValue());
462
463 ((MP4BitfieldProperty*)m_pProperties[i])->SetNumBits(timeStampLength);
464
465 // handle a nonsensical situation gracefully
466 if (timeStampLength == 0) {
467 m_pProperties[i]->SetImplicit(true);
468 }
469 }
470 }
471
MP4ContentIdDescriptor()472 MP4ContentIdDescriptor::MP4ContentIdDescriptor()
473 : MP4Descriptor(MP4ContentIdDescrTag)
474 {
475 AddProperty( /* 0 */
476 new MP4BitfieldProperty("compatibility", 2));
477 AddProperty( /* 1 */
478 new MP4BitfieldProperty("contentTypeFlag", 1));
479 AddProperty( /* 2 */
480 new MP4BitfieldProperty("contentIdFlag", 1));
481 AddProperty( /* 3 */
482 new MP4BitfieldProperty("protectedContent", 1));
483 AddProperty( /* 4 */
484 new MP4BitfieldProperty("reserved", 3));
485 AddProperty( /* 5 */
486 new MP4Integer8Property("contentType"));
487 AddProperty( /* 6 */
488 new MP4Integer8Property("contentIdType"));
489 AddProperty( /* 7 */
490 new MP4BytesProperty("contentId"));
491 }
492
Read(MP4File * pFile)493 void MP4ContentIdDescriptor::Read(MP4File* pFile)
494 {
495 ReadHeader(pFile);
496
497 /* read the first property, 'compatiblity' */
498 ReadProperties(pFile, 0, 1);
499
500 /* if compatiblity != 0 */
501 if (((MP4Integer8Property*)m_pProperties[0])->GetValue() != 0) {
502 /* we don't understand it */
503 VERBOSE_READ(pFile->GetVerbosity(),
504 printf("incompatible content id descriptor\n"));
505 return;
506 }
507
508 /* read the next four properties */
509 ReadProperties(pFile, 1, 4);
510
511 /* which allows us to reconfigure ourselves */
512 Mutate();
513
514 bool contentTypeFlag = ((MP4BitfieldProperty*)m_pProperties[1])->GetValue();
515
516 bool contentIdFlag = ((MP4BitfieldProperty*)m_pProperties[2])->GetValue();
517
518 if (contentIdFlag) {
519
520 u_int32_t cIdOffset = 2;
521
522 if (contentTypeFlag) {
523
524 cIdOffset++;
525
526 }
527
528 ((MP4BytesProperty*)m_pProperties[7])->SetValueSize(m_size - cIdOffset);
529
530 }
531
532
533
534 /* read the remaining properties */
535 ReadProperties(pFile, 5);
536 }
537
Mutate()538 void MP4ContentIdDescriptor::Mutate()
539 {
540 bool contentTypeFlag = ((MP4BitfieldProperty*)m_pProperties[1])->GetValue();
541 m_pProperties[5]->SetImplicit(!contentTypeFlag);
542
543 bool contentIdFlag = ((MP4BitfieldProperty*)m_pProperties[2])->GetValue();
544 m_pProperties[6]->SetImplicit(!contentIdFlag);
545 m_pProperties[7]->SetImplicit(!contentIdFlag);
546
547 }
548
CreateDescriptor(u_int8_t tag)549 MP4Descriptor* MP4DescriptorProperty::CreateDescriptor(u_int8_t tag)
550 {
551 MP4Descriptor* pDescriptor = NULL;
552
553 switch (tag) {
554 case MP4ESDescrTag:
555 pDescriptor = new MP4ESDescriptor();
556 break;
557 case MP4DecConfigDescrTag:
558 pDescriptor = new MP4DecConfigDescriptor();
559 break;
560 case MP4DecSpecificDescrTag:
561 case MP4IPMPDescrTag:
562 case MP4RegistrationDescrTag:
563 pDescriptor = new MP4BytesDescriptor(tag);
564 break;
565 case MP4SLConfigDescrTag:
566 pDescriptor = new MP4SLConfigDescriptor();
567 break;
568 case MP4ContentIdDescrTag:
569 pDescriptor = new MP4ContentIdDescriptor();
570 break;
571 case MP4ESIDIncDescrTag:
572 case MP4ESIDRefDescrTag:
573 case MP4IPIPtrDescrTag:
574 case MP4SupplContentIdDescrTag:
575 case MP4IPMPPtrDescrTag:
576 case MP4ExtProfileLevelDescrTag:
577 pDescriptor = new MP4BaseDescriptor(tag);
578 break;
579 case MP4QosDescrTag:
580 pDescriptor = new MP4QosDescriptorBase(MP4QosDescrTag);
581 break;
582 case MP4IODescrTag:
583 case MP4FileIODescrTag:
584 pDescriptor = new MP4IODescriptor();
585 pDescriptor->SetTag(tag);
586 break;
587 case MP4ODescrTag:
588 case MP4FileODescrTag:
589 pDescriptor = new MP4ODescriptor();
590 pDescriptor->SetTag(tag);
591 break;
592 }
593
594 if (pDescriptor == NULL) {
595 if (tag >= MP4OCIDescrTagsStart && tag <= MP4OCIDescrTagsEnd) {
596 pDescriptor = CreateOCIDescriptor(tag);
597 }
598
599 if (tag >= MP4ExtDescrTagsStart && tag <= MP4ExtDescrTagsEnd) {
600 pDescriptor = new MP4BytesDescriptor(tag);
601 }
602 }
603
604 return pDescriptor;
605 }
606
607