1 /*
2 *
3 * BlueZ - Bluetooth protocol stack for Linux
4 *
5 * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
6 *
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 *
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <string.h>
29
30 #include "gstsbcutil.h"
31 #include "gstsbcenc.h"
32
33 #define SBC_ENC_DEFAULT_MODE SBC_MODE_AUTO
34 #define SBC_ENC_DEFAULT_BLOCKS 0
35 #define SBC_ENC_DEFAULT_SUB_BANDS 0
36 #define SBC_ENC_DEFAULT_ALLOCATION SBC_AM_AUTO
37 #define SBC_ENC_DEFAULT_RATE 0
38 #define SBC_ENC_DEFAULT_CHANNELS 0
39
40 #define SBC_ENC_BITPOOL_AUTO 1
41 #define SBC_ENC_BITPOOL_MIN 2
42 #define SBC_ENC_BITPOOL_MIN_STR "2"
43 #define SBC_ENC_BITPOOL_MAX 64
44 #define SBC_ENC_BITPOOL_MAX_STR "64"
45
46 GST_DEBUG_CATEGORY_STATIC(sbc_enc_debug);
47 #define GST_CAT_DEFAULT sbc_enc_debug
48
49 #define GST_TYPE_SBC_MODE (gst_sbc_mode_get_type())
50
gst_sbc_mode_get_type(void)51 static GType gst_sbc_mode_get_type(void)
52 {
53 static GType sbc_mode_type = 0;
54 static GEnumValue sbc_modes[] = {
55 { SBC_MODE_MONO, "Mono", "mono" },
56 { SBC_MODE_DUAL_CHANNEL, "Dual Channel", "dual" },
57 { SBC_MODE_STEREO, "Stereo", "stereo"},
58 { SBC_MODE_JOINT_STEREO, "Joint Stereo", "joint" },
59 { SBC_MODE_AUTO, "Auto", "auto" },
60 { -1, NULL, NULL}
61 };
62
63 if (!sbc_mode_type)
64 sbc_mode_type = g_enum_register_static("GstSbcMode", sbc_modes);
65
66 return sbc_mode_type;
67 }
68
69 #define GST_TYPE_SBC_ALLOCATION (gst_sbc_allocation_get_type())
70
gst_sbc_allocation_get_type(void)71 static GType gst_sbc_allocation_get_type(void)
72 {
73 static GType sbc_allocation_type = 0;
74 static GEnumValue sbc_allocations[] = {
75 { SBC_AM_LOUDNESS, "Loudness", "loudness" },
76 { SBC_AM_SNR, "SNR", "snr" },
77 { SBC_AM_AUTO, "Auto", "auto" },
78 { -1, NULL, NULL}
79 };
80
81 if (!sbc_allocation_type)
82 sbc_allocation_type = g_enum_register_static(
83 "GstSbcAllocation", sbc_allocations);
84
85 return sbc_allocation_type;
86 }
87
88 #define GST_TYPE_SBC_BLOCKS (gst_sbc_blocks_get_type())
89
gst_sbc_blocks_get_type(void)90 static GType gst_sbc_blocks_get_type(void)
91 {
92 static GType sbc_blocks_type = 0;
93 static GEnumValue sbc_blocks[] = {
94 { 0, "Auto", "auto" },
95 { 4, "4", "4" },
96 { 8, "8", "8" },
97 { 12, "12", "12" },
98 { 16, "16", "16" },
99 { -1, NULL, NULL}
100 };
101
102 if (!sbc_blocks_type)
103 sbc_blocks_type = g_enum_register_static(
104 "GstSbcBlocks", sbc_blocks);
105
106 return sbc_blocks_type;
107 }
108
109 #define GST_TYPE_SBC_SUBBANDS (gst_sbc_subbands_get_type())
110
gst_sbc_subbands_get_type(void)111 static GType gst_sbc_subbands_get_type(void)
112 {
113 static GType sbc_subbands_type = 0;
114 static GEnumValue sbc_subbands[] = {
115 { 0, "Auto", "auto" },
116 { 4, "4 subbands", "4" },
117 { 8, "8 subbands", "8" },
118 { -1, NULL, NULL}
119 };
120
121 if (!sbc_subbands_type)
122 sbc_subbands_type = g_enum_register_static(
123 "GstSbcSubbands", sbc_subbands);
124
125 return sbc_subbands_type;
126 }
127
128 enum {
129 PROP_0,
130 PROP_MODE,
131 PROP_ALLOCATION,
132 PROP_BLOCKS,
133 PROP_SUBBANDS,
134 PROP_BITPOOL
135 };
136
137 GST_BOILERPLATE(GstSbcEnc, gst_sbc_enc, GstElement, GST_TYPE_ELEMENT);
138
139 static const GstElementDetails sbc_enc_details =
140 GST_ELEMENT_DETAILS("Bluetooth SBC encoder",
141 "Codec/Encoder/Audio",
142 "Encode a SBC audio stream",
143 "Marcel Holtmann <marcel@holtmann.org>");
144
145 static GstStaticPadTemplate sbc_enc_sink_factory =
146 GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
147 GST_STATIC_CAPS("audio/x-raw-int, "
148 "rate = (int) { 16000, 32000, 44100, 48000 }, "
149 "channels = (int) [ 1, 2 ], "
150 "endianness = (int) BYTE_ORDER, "
151 "signed = (boolean) true, "
152 "width = (int) 16, "
153 "depth = (int) 16"));
154
155 static GstStaticPadTemplate sbc_enc_src_factory =
156 GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
157 GST_STATIC_CAPS("audio/x-sbc, "
158 "rate = (int) { 16000, 32000, 44100, 48000 }, "
159 "channels = (int) [ 1, 2 ], "
160 "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
161 "blocks = (int) { 4, 8, 12, 16 }, "
162 "subbands = (int) { 4, 8 }, "
163 "allocation = (string) { \"snr\", \"loudness\" }, "
164 "bitpool = (int) [ " SBC_ENC_BITPOOL_MIN_STR
165 ", " SBC_ENC_BITPOOL_MAX_STR " ]"));
166
167 gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps);
168
sbc_enc_generate_srcpad_caps(GstSbcEnc * enc)169 static GstCaps *sbc_enc_generate_srcpad_caps(GstSbcEnc *enc)
170 {
171 GstCaps *src_caps;
172 GstStructure *structure;
173 GEnumValue *enum_value;
174 GEnumClass *enum_class;
175 GValue *value;
176
177 src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad));
178 structure = gst_caps_get_structure(src_caps, 0);
179
180 value = g_new0(GValue, 1);
181
182 if (enc->rate != 0)
183 gst_sbc_util_set_structure_int_param(structure, "rate",
184 enc->rate, value);
185
186 if (enc->channels != 0)
187 gst_sbc_util_set_structure_int_param(structure, "channels",
188 enc->channels, value);
189
190 if (enc->subbands != 0)
191 gst_sbc_util_set_structure_int_param(structure, "subbands",
192 enc->subbands, value);
193
194 if (enc->blocks != 0)
195 gst_sbc_util_set_structure_int_param(structure, "blocks",
196 enc->blocks, value);
197
198 if (enc->bitpool != SBC_ENC_BITPOOL_AUTO)
199 gst_sbc_util_set_structure_int_param(structure, "bitpool",
200 enc->bitpool, value);
201
202 if (enc->mode != SBC_ENC_DEFAULT_MODE) {
203 enum_class = g_type_class_ref(GST_TYPE_SBC_MODE);
204 enum_value = g_enum_get_value(enum_class, enc->mode);
205 gst_sbc_util_set_structure_string_param(structure, "mode",
206 enum_value->value_nick, value);
207 g_type_class_unref(enum_class);
208 }
209
210 if (enc->allocation != SBC_AM_AUTO) {
211 enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION);
212 enum_value = g_enum_get_value(enum_class, enc->allocation);
213 gst_sbc_util_set_structure_string_param(structure, "allocation",
214 enum_value->value_nick, value);
215 g_type_class_unref(enum_class);
216 }
217
218 g_free(value);
219
220 return src_caps;
221 }
222
sbc_enc_src_getcaps(GstPad * pad)223 static GstCaps *sbc_enc_src_getcaps(GstPad *pad)
224 {
225 GstSbcEnc *enc;
226
227 enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
228
229 return sbc_enc_generate_srcpad_caps(enc);
230 }
231
sbc_enc_src_setcaps(GstPad * pad,GstCaps * caps)232 static gboolean sbc_enc_src_setcaps(GstPad *pad, GstCaps *caps)
233 {
234 GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
235
236 GST_LOG_OBJECT(enc, "setting srcpad caps");
237
238 return gst_sbc_enc_fill_sbc_params(enc, caps);
239 }
240
sbc_enc_src_caps_fixate(GstSbcEnc * enc,GstCaps * caps)241 static GstCaps *sbc_enc_src_caps_fixate(GstSbcEnc *enc, GstCaps *caps)
242 {
243 gchar *error_message = NULL;
244 GstCaps *result;
245
246 result = gst_sbc_util_caps_fixate(caps, &error_message);
247
248 if (!result) {
249 GST_WARNING_OBJECT(enc, "Invalid input caps caused parsing "
250 "error: %s", error_message);
251 g_free(error_message);
252 return NULL;
253 }
254
255 return result;
256 }
257
sbc_enc_get_fixed_srcpad_caps(GstSbcEnc * enc)258 static GstCaps *sbc_enc_get_fixed_srcpad_caps(GstSbcEnc *enc)
259 {
260 GstCaps *caps;
261 gboolean res = TRUE;
262 GstCaps *result_caps = NULL;
263
264 caps = gst_pad_get_allowed_caps(enc->srcpad);
265 if (caps == NULL)
266 caps = sbc_enc_src_getcaps(enc->srcpad);
267
268 if (caps == GST_CAPS_NONE || gst_caps_is_empty(caps)) {
269 res = FALSE;
270 goto done;
271 }
272
273 result_caps = sbc_enc_src_caps_fixate(enc, caps);
274
275 done:
276 gst_caps_unref(caps);
277
278 if (!res)
279 return NULL;
280
281 return result_caps;
282 }
283
sbc_enc_sink_setcaps(GstPad * pad,GstCaps * caps)284 static gboolean sbc_enc_sink_setcaps(GstPad *pad, GstCaps *caps)
285 {
286 GstSbcEnc *enc;
287 GstStructure *structure;
288 GstCaps *src_caps;
289 gint rate, channels;
290 gboolean res;
291
292 enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
293 structure = gst_caps_get_structure(caps, 0);
294
295 if (!gst_structure_get_int(structure, "rate", &rate))
296 return FALSE;
297 if (!gst_structure_get_int(structure, "channels", &channels))
298 return FALSE;
299
300 enc->rate = rate;
301 enc->channels = channels;
302
303 src_caps = sbc_enc_get_fixed_srcpad_caps(enc);
304 if (!src_caps)
305 return FALSE;
306 res = gst_pad_set_caps(enc->srcpad, src_caps);
307 gst_caps_unref(src_caps);
308
309 return res;
310 }
311
gst_sbc_enc_fill_sbc_params(GstSbcEnc * enc,GstCaps * caps)312 gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps)
313 {
314 if (!gst_caps_is_fixed(caps)) {
315 GST_DEBUG_OBJECT(enc, "didn't receive fixed caps, "
316 "returning false");
317 return FALSE;
318 }
319
320 if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps))
321 return FALSE;
322
323 if (enc->rate != 0 && gst_sbc_parse_rate_from_sbc(enc->sbc.frequency)
324 != enc->rate)
325 goto fail;
326
327 if (enc->channels != 0 && gst_sbc_get_channel_number(enc->sbc.mode)
328 != enc->channels)
329 goto fail;
330
331 if (enc->blocks != 0 && gst_sbc_parse_blocks_from_sbc(enc->sbc.blocks)
332 != enc->blocks)
333 goto fail;
334
335 if (enc->subbands != 0 && gst_sbc_parse_subbands_from_sbc(
336 enc->sbc.subbands) != enc->subbands)
337 goto fail;
338
339 if (enc->mode != SBC_ENC_DEFAULT_MODE && enc->sbc.mode != enc->mode)
340 goto fail;
341
342 if (enc->allocation != SBC_AM_AUTO &&
343 enc->sbc.allocation != enc->allocation)
344 goto fail;
345
346 if (enc->bitpool != SBC_ENC_BITPOOL_AUTO &&
347 enc->sbc.bitpool != enc->bitpool)
348 goto fail;
349
350 enc->codesize = sbc_get_codesize(&enc->sbc);
351 enc->frame_length = sbc_get_frame_length(&enc->sbc);
352 enc->frame_duration = sbc_get_frame_duration(&enc->sbc);
353
354 GST_DEBUG_OBJECT(enc, "codesize: %d, frame_length: %d, frame_duration:"
355 " %d", enc->codesize, enc->frame_length,
356 enc->frame_duration);
357
358 return TRUE;
359
360 fail:
361 memset(&enc->sbc, 0, sizeof(sbc_t));
362 return FALSE;
363 }
364
sbc_enc_chain(GstPad * pad,GstBuffer * buffer)365 static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer)
366 {
367 GstSbcEnc *enc = GST_SBC_ENC(gst_pad_get_parent(pad));
368 GstAdapter *adapter = enc->adapter;
369 GstFlowReturn res = GST_FLOW_OK;
370
371 gst_adapter_push(adapter, buffer);
372
373 while (gst_adapter_available(adapter) >= enc->codesize &&
374 res == GST_FLOW_OK) {
375 GstBuffer *output;
376 GstCaps *caps;
377 const guint8 *data;
378 gint consumed;
379
380 caps = GST_PAD_CAPS(enc->srcpad);
381 res = gst_pad_alloc_buffer_and_set_caps(enc->srcpad,
382 GST_BUFFER_OFFSET_NONE,
383 enc->frame_length, caps,
384 &output);
385 if (res != GST_FLOW_OK)
386 goto done;
387
388 data = gst_adapter_peek(adapter, enc->codesize);
389
390 consumed = sbc_encode(&enc->sbc, (gpointer) data,
391 enc->codesize,
392 GST_BUFFER_DATA(output),
393 GST_BUFFER_SIZE(output), NULL);
394 if (consumed <= 0) {
395 GST_DEBUG_OBJECT(enc, "comsumed < 0, codesize: %d",
396 enc->codesize);
397 break;
398 }
399 gst_adapter_flush(adapter, consumed);
400
401 GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer);
402 /* we have only 1 frame */
403 GST_BUFFER_DURATION(output) = enc->frame_duration;
404
405 res = gst_pad_push(enc->srcpad, output);
406
407 if (res != GST_FLOW_OK)
408 goto done;
409 }
410
411 done:
412 gst_object_unref(enc);
413
414 return res;
415 }
416
sbc_enc_change_state(GstElement * element,GstStateChange transition)417 static GstStateChangeReturn sbc_enc_change_state(GstElement *element,
418 GstStateChange transition)
419 {
420 GstSbcEnc *enc = GST_SBC_ENC(element);
421
422 switch (transition) {
423 case GST_STATE_CHANGE_READY_TO_PAUSED:
424 GST_DEBUG("Setup subband codec");
425 sbc_init(&enc->sbc, 0);
426 break;
427
428 case GST_STATE_CHANGE_PAUSED_TO_READY:
429 GST_DEBUG("Finish subband codec");
430 sbc_finish(&enc->sbc);
431 break;
432
433 default:
434 break;
435 }
436
437 return parent_class->change_state(element, transition);
438 }
439
gst_sbc_enc_dispose(GObject * object)440 static void gst_sbc_enc_dispose(GObject *object)
441 {
442 GstSbcEnc *enc = GST_SBC_ENC(object);
443
444 if (enc->adapter != NULL)
445 g_object_unref(G_OBJECT(enc->adapter));
446
447 enc->adapter = NULL;
448 }
449
gst_sbc_enc_base_init(gpointer g_class)450 static void gst_sbc_enc_base_init(gpointer g_class)
451 {
452 GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
453
454 gst_element_class_add_pad_template(element_class,
455 gst_static_pad_template_get(&sbc_enc_sink_factory));
456
457 gst_element_class_add_pad_template(element_class,
458 gst_static_pad_template_get(&sbc_enc_src_factory));
459
460 gst_element_class_set_details(element_class, &sbc_enc_details);
461 }
462
gst_sbc_enc_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)463 static void gst_sbc_enc_set_property(GObject *object, guint prop_id,
464 const GValue *value, GParamSpec *pspec)
465 {
466 GstSbcEnc *enc = GST_SBC_ENC(object);
467
468 /* changes to those properties will only happen on the next caps
469 * negotiation */
470
471 switch (prop_id) {
472 case PROP_MODE:
473 enc->mode = g_value_get_enum(value);
474 break;
475 case PROP_ALLOCATION:
476 enc->allocation = g_value_get_enum(value);
477 break;
478 case PROP_BLOCKS:
479 enc->blocks = g_value_get_enum(value);
480 break;
481 case PROP_SUBBANDS:
482 enc->subbands = g_value_get_enum(value);
483 break;
484 case PROP_BITPOOL:
485 enc->bitpool = g_value_get_int(value);
486 break;
487 default:
488 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
489 break;
490 }
491 }
492
gst_sbc_enc_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)493 static void gst_sbc_enc_get_property(GObject *object, guint prop_id,
494 GValue *value, GParamSpec *pspec)
495 {
496 GstSbcEnc *enc = GST_SBC_ENC(object);
497
498 switch (prop_id) {
499 case PROP_MODE:
500 g_value_set_enum(value, enc->mode);
501 break;
502 case PROP_ALLOCATION:
503 g_value_set_enum(value, enc->allocation);
504 break;
505 case PROP_BLOCKS:
506 g_value_set_enum(value, enc->blocks);
507 break;
508 case PROP_SUBBANDS:
509 g_value_set_enum(value, enc->subbands);
510 break;
511 case PROP_BITPOOL:
512 g_value_set_int(value, enc->bitpool);
513 break;
514 default:
515 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
516 break;
517 }
518 }
519
gst_sbc_enc_class_init(GstSbcEncClass * klass)520 static void gst_sbc_enc_class_init(GstSbcEncClass *klass)
521 {
522 GObjectClass *object_class = G_OBJECT_CLASS(klass);
523 GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
524
525 parent_class = g_type_class_peek_parent(klass);
526
527 object_class->set_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_set_property);
528 object_class->get_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_get_property);
529 object_class->dispose = GST_DEBUG_FUNCPTR(gst_sbc_enc_dispose);
530
531 element_class->change_state = GST_DEBUG_FUNCPTR(sbc_enc_change_state);
532
533 g_object_class_install_property(object_class, PROP_MODE,
534 g_param_spec_enum("mode", "Mode",
535 "Encoding mode", GST_TYPE_SBC_MODE,
536 SBC_ENC_DEFAULT_MODE, G_PARAM_READWRITE));
537
538 g_object_class_install_property(object_class, PROP_ALLOCATION,
539 g_param_spec_enum("allocation", "Allocation",
540 "Allocation method", GST_TYPE_SBC_ALLOCATION,
541 SBC_ENC_DEFAULT_ALLOCATION, G_PARAM_READWRITE));
542
543 g_object_class_install_property(object_class, PROP_BLOCKS,
544 g_param_spec_enum("blocks", "Blocks",
545 "Blocks", GST_TYPE_SBC_BLOCKS,
546 SBC_ENC_DEFAULT_BLOCKS, G_PARAM_READWRITE));
547
548 g_object_class_install_property(object_class, PROP_SUBBANDS,
549 g_param_spec_enum("subbands", "Sub bands",
550 "Number of sub bands", GST_TYPE_SBC_SUBBANDS,
551 SBC_ENC_DEFAULT_SUB_BANDS, G_PARAM_READWRITE));
552
553 g_object_class_install_property(object_class, PROP_BITPOOL,
554 g_param_spec_int("bitpool", "Bitpool",
555 "Bitpool (use 1 for automatic selection)",
556 SBC_ENC_BITPOOL_AUTO, SBC_ENC_BITPOOL_MAX,
557 SBC_ENC_BITPOOL_AUTO, G_PARAM_READWRITE));
558
559 GST_DEBUG_CATEGORY_INIT(sbc_enc_debug, "sbcenc", 0,
560 "SBC encoding element");
561 }
562
gst_sbc_enc_init(GstSbcEnc * self,GstSbcEncClass * klass)563 static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass)
564 {
565 self->sinkpad = gst_pad_new_from_static_template(
566 &sbc_enc_sink_factory, "sink");
567 gst_pad_set_setcaps_function(self->sinkpad,
568 GST_DEBUG_FUNCPTR(sbc_enc_sink_setcaps));
569 gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
570
571 self->srcpad = gst_pad_new_from_static_template(
572 &sbc_enc_src_factory, "src");
573 gst_pad_set_getcaps_function(self->srcpad,
574 GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps));
575 gst_pad_set_setcaps_function(self->srcpad,
576 GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps));
577 gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
578
579 gst_pad_set_chain_function(self->sinkpad,
580 GST_DEBUG_FUNCPTR(sbc_enc_chain));
581
582 self->subbands = SBC_ENC_DEFAULT_SUB_BANDS;
583 self->blocks = SBC_ENC_DEFAULT_BLOCKS;
584 self->mode = SBC_ENC_DEFAULT_MODE;
585 self->allocation = SBC_ENC_DEFAULT_ALLOCATION;
586 self->rate = SBC_ENC_DEFAULT_RATE;
587 self->channels = SBC_ENC_DEFAULT_CHANNELS;
588 self->bitpool = SBC_ENC_BITPOOL_AUTO;
589
590 self->frame_length = 0;
591 self->frame_duration = 0;
592
593 self->adapter = gst_adapter_new();
594 }
595
gst_sbc_enc_plugin_init(GstPlugin * plugin)596 gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin)
597 {
598 return gst_element_register(plugin, "sbcenc",
599 GST_RANK_NONE, GST_TYPE_SBC_ENC);
600 }
601
602
603