1 /* 2 * Copyright (c) 2010 The WebM project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 12 #include <stdlib.h> 13 #include <string.h> 14 #include "vpx/vpx_decoder.h" 15 #include "vpx/vp8dx.h" 16 #include "vpx/internal/vpx_codec_internal.h" 17 #include "./vpx_version.h" 18 #include "vp9/common/vp9_frame_buffers.h" 19 #include "vp9/decoder/vp9_decoder.h" 20 #include "vp9/decoder/vp9_read_bit_buffer.h" 21 #include "vp9/vp9_iface_common.h" 22 23 #define VP9_CAP_POSTPROC (CONFIG_VP9_POSTPROC ? VPX_CODEC_CAP_POSTPROC : 0) 24 typedef vpx_codec_stream_info_t vp9_stream_info_t; 25 26 struct vpx_codec_alg_priv { 27 vpx_codec_priv_t base; 28 vpx_codec_dec_cfg_t cfg; 29 vp9_stream_info_t si; 30 int decoder_init; 31 struct VP9Decompressor *pbi; 32 int postproc_cfg_set; 33 vp8_postproc_cfg_t postproc_cfg; 34 #if CONFIG_POSTPROC_VISUALIZER 35 unsigned int dbg_postproc_flag; 36 int dbg_color_ref_frame_flag; 37 int dbg_color_mb_modes_flag; 38 int dbg_color_b_modes_flag; 39 int dbg_display_mv_flag; 40 #endif 41 vpx_image_t img; 42 int img_setup; 43 int img_avail; 44 int invert_tile_order; 45 46 // External frame buffer info to save for VP9 common. 47 void *ext_priv; // Private data associated with the external frame buffers. 48 vpx_get_frame_buffer_cb_fn_t get_ext_fb_cb; 49 vpx_release_frame_buffer_cb_fn_t release_ext_fb_cb; 50 }; 51 52 static vpx_codec_err_t vp9_init(vpx_codec_ctx_t *ctx, 53 vpx_codec_priv_enc_mr_cfg_t *data) { 54 // This function only allocates space for the vpx_codec_alg_priv_t 55 // structure. More memory may be required at the time the stream 56 // information becomes known. 57 (void)data; 58 if (!ctx->priv) { 59 void *base = vpx_memalign(32, sizeof(vpx_codec_alg_priv_t)); 60 if (base == NULL) 61 return VPX_CODEC_MEM_ERROR; 62 63 memset(base, 0, sizeof(vpx_codec_alg_priv_t)); 64 ctx->priv = (vpx_codec_priv_t *)base; 65 ctx->priv->sz = sizeof(*ctx->priv); 66 ctx->priv->iface = ctx->iface; 67 ctx->priv->alg_priv = (vpx_codec_alg_priv_t *)base; 68 ctx->priv->alg_priv->si.sz = sizeof(ctx->priv->alg_priv->si); 69 ctx->priv->init_flags = ctx->init_flags; 70 71 if (ctx->config.dec) { 72 // Update the reference to the config structure to an internal copy. 73 ctx->priv->alg_priv->cfg = *ctx->config.dec; 74 ctx->config.dec = &ctx->priv->alg_priv->cfg; 75 } 76 } 77 78 return VPX_CODEC_OK; 79 } 80 81 static vpx_codec_err_t vp9_destroy(vpx_codec_alg_priv_t *ctx) { 82 if (ctx->pbi) 83 vp9_remove_decompressor(ctx->pbi); 84 85 return VPX_CODEC_OK; 86 } 87 88 static vpx_codec_err_t vp9_peek_si(const uint8_t *data, unsigned int data_sz, 89 vpx_codec_stream_info_t *si) { 90 if (data_sz <= 8) return VPX_CODEC_UNSUP_BITSTREAM; 91 if (data + data_sz <= data) return VPX_CODEC_INVALID_PARAM; 92 93 si->is_kf = 0; 94 si->w = si->h = 0; 95 96 { 97 struct vp9_read_bit_buffer rb = { data, data + data_sz, 0, NULL, NULL }; 98 const int frame_marker = vp9_rb_read_literal(&rb, 2); 99 const int version = vp9_rb_read_bit(&rb); 100 (void) vp9_rb_read_bit(&rb); // unused version bit 101 102 if (frame_marker != VP9_FRAME_MARKER) 103 return VPX_CODEC_UNSUP_BITSTREAM; 104 if (version > 1) return VPX_CODEC_UNSUP_BITSTREAM; 105 106 if (vp9_rb_read_bit(&rb)) { // show an existing frame 107 return VPX_CODEC_OK; 108 } 109 110 si->is_kf = !vp9_rb_read_bit(&rb); 111 if (si->is_kf) { 112 const int sRGB = 7; 113 int colorspace; 114 115 rb.bit_offset += 1; // show frame 116 rb.bit_offset += 1; // error resilient 117 118 if (vp9_rb_read_literal(&rb, 8) != VP9_SYNC_CODE_0 || 119 vp9_rb_read_literal(&rb, 8) != VP9_SYNC_CODE_1 || 120 vp9_rb_read_literal(&rb, 8) != VP9_SYNC_CODE_2) { 121 return VPX_CODEC_UNSUP_BITSTREAM; 122 } 123 124 colorspace = vp9_rb_read_literal(&rb, 3); 125 if (colorspace != sRGB) { 126 rb.bit_offset += 1; // [16,235] (including xvycc) vs [0,255] range 127 if (version == 1) { 128 rb.bit_offset += 2; // subsampling x/y 129 rb.bit_offset += 1; // has extra plane 130 } 131 } else { 132 if (version == 1) { 133 rb.bit_offset += 1; // has extra plane 134 } else { 135 // RGB is only available in version 1 136 return VPX_CODEC_UNSUP_BITSTREAM; 137 } 138 } 139 140 // TODO(jzern): these are available on non-keyframes in intra only mode. 141 si->w = vp9_rb_read_literal(&rb, 16) + 1; 142 si->h = vp9_rb_read_literal(&rb, 16) + 1; 143 } 144 } 145 146 return VPX_CODEC_OK; 147 } 148 149 static vpx_codec_err_t vp9_get_si(vpx_codec_alg_priv_t *ctx, 150 vpx_codec_stream_info_t *si) { 151 const size_t sz = (si->sz >= sizeof(vp9_stream_info_t)) 152 ? sizeof(vp9_stream_info_t) 153 : sizeof(vpx_codec_stream_info_t); 154 memcpy(si, &ctx->si, sz); 155 si->sz = (unsigned int)sz; 156 157 return VPX_CODEC_OK; 158 } 159 160 161 static vpx_codec_err_t update_error_state(vpx_codec_alg_priv_t *ctx, 162 const struct vpx_internal_error_info *error) { 163 if (error->error_code) 164 ctx->base.err_detail = error->has_detail ? error->detail : NULL; 165 166 return error->error_code; 167 } 168 169 static vpx_codec_err_t decode_one(vpx_codec_alg_priv_t *ctx, 170 const uint8_t **data, unsigned int data_sz, 171 void *user_priv, int64_t deadline) { 172 vpx_codec_err_t res = VPX_CODEC_OK; 173 174 ctx->img_avail = 0; 175 176 // Determine the stream parameters. Note that we rely on peek_si to 177 // validate that we have a buffer that does not wrap around the top 178 // of the heap. 179 if (!ctx->si.h) 180 res = ctx->base.iface->dec.peek_si(*data, data_sz, &ctx->si); 181 182 /* Initialize the decoder instance on the first frame*/ 183 if (!res && !ctx->decoder_init) { 184 VP9D_CONFIG oxcf; 185 struct VP9Decompressor *optr; 186 187 vp9_initialize_dec(); 188 189 oxcf.width = ctx->si.w; 190 oxcf.height = ctx->si.h; 191 oxcf.version = 9; 192 oxcf.max_threads = ctx->cfg.threads; 193 oxcf.inv_tile_order = ctx->invert_tile_order; 194 optr = vp9_create_decompressor(&oxcf); 195 196 // If postprocessing was enabled by the application and a 197 // configuration has not been provided, default it. 198 if (!ctx->postproc_cfg_set && 199 (ctx->base.init_flags & VPX_CODEC_USE_POSTPROC)) { 200 ctx->postproc_cfg.post_proc_flag = VP8_DEBLOCK | VP8_DEMACROBLOCK; 201 ctx->postproc_cfg.deblocking_level = 4; 202 ctx->postproc_cfg.noise_level = 0; 203 } 204 205 if (!optr) { 206 res = VPX_CODEC_ERROR; 207 } else { 208 VP9D_COMP *const pbi = (VP9D_COMP*)optr; 209 VP9_COMMON *const cm = &pbi->common; 210 211 // Set index to not initialized. 212 cm->new_fb_idx = -1; 213 214 if (ctx->get_ext_fb_cb != NULL && ctx->release_ext_fb_cb != NULL) { 215 cm->get_fb_cb = ctx->get_ext_fb_cb; 216 cm->release_fb_cb = ctx->release_ext_fb_cb; 217 cm->cb_priv = ctx->ext_priv; 218 } else { 219 cm->get_fb_cb = vp9_get_frame_buffer; 220 cm->release_fb_cb = vp9_release_frame_buffer; 221 222 if (vp9_alloc_internal_frame_buffers(&cm->int_frame_buffers)) 223 vpx_internal_error(&cm->error, VPX_CODEC_MEM_ERROR, 224 "Failed to initialize internal frame buffers"); 225 cm->cb_priv = &cm->int_frame_buffers; 226 } 227 228 ctx->pbi = optr; 229 } 230 231 ctx->decoder_init = 1; 232 } 233 234 if (!res && ctx->pbi) { 235 VP9D_COMP *const pbi = ctx->pbi; 236 VP9_COMMON *const cm = &pbi->common; 237 YV12_BUFFER_CONFIG sd; 238 int64_t time_stamp = 0, time_end_stamp = 0; 239 vp9_ppflags_t flags = {0}; 240 241 if (ctx->base.init_flags & VPX_CODEC_USE_POSTPROC) { 242 flags.post_proc_flag = 243 #if CONFIG_POSTPROC_VISUALIZER 244 (ctx->dbg_color_ref_frame_flag ? VP9D_DEBUG_CLR_FRM_REF_BLKS : 0) | 245 (ctx->dbg_color_mb_modes_flag ? VP9D_DEBUG_CLR_BLK_MODES : 0) | 246 (ctx->dbg_color_b_modes_flag ? VP9D_DEBUG_CLR_BLK_MODES : 0) | 247 (ctx->dbg_display_mv_flag ? VP9D_DEBUG_DRAW_MV : 0) | 248 #endif 249 ctx->postproc_cfg.post_proc_flag; 250 251 flags.deblocking_level = ctx->postproc_cfg.deblocking_level; 252 flags.noise_level = ctx->postproc_cfg.noise_level; 253 #if CONFIG_POSTPROC_VISUALIZER 254 flags.display_ref_frame_flag = ctx->dbg_color_ref_frame_flag; 255 flags.display_mb_modes_flag = ctx->dbg_color_mb_modes_flag; 256 flags.display_b_modes_flag = ctx->dbg_color_b_modes_flag; 257 flags.display_mv_flag = ctx->dbg_display_mv_flag; 258 #endif 259 } 260 261 if (vp9_receive_compressed_data(pbi, data_sz, data, deadline)) 262 res = update_error_state(ctx, &cm->error); 263 264 if (!res && 0 == vp9_get_raw_frame(pbi, &sd, &time_stamp, 265 &time_end_stamp, &flags)) { 266 yuvconfig2image(&ctx->img, &sd, user_priv); 267 268 ctx->img.fb_priv = cm->frame_bufs[cm->new_fb_idx].raw_frame_buffer.priv; 269 ctx->img_avail = 1; 270 } 271 } 272 273 return res; 274 } 275 276 static void parse_superframe_index(const uint8_t *data, size_t data_sz, 277 uint32_t sizes[8], int *count) { 278 uint8_t marker; 279 280 assert(data_sz); 281 marker = data[data_sz - 1]; 282 *count = 0; 283 284 if ((marker & 0xe0) == 0xc0) { 285 const uint32_t frames = (marker & 0x7) + 1; 286 const uint32_t mag = ((marker >> 3) & 0x3) + 1; 287 const size_t index_sz = 2 + mag * frames; 288 289 if (data_sz >= index_sz && data[data_sz - index_sz] == marker) { 290 // found a valid superframe index 291 uint32_t i, j; 292 const uint8_t *x = data + data_sz - index_sz + 1; 293 294 for (i = 0; i < frames; i++) { 295 uint32_t this_sz = 0; 296 297 for (j = 0; j < mag; j++) 298 this_sz |= (*x++) << (j * 8); 299 sizes[i] = this_sz; 300 } 301 302 *count = frames; 303 } 304 } 305 } 306 307 static vpx_codec_err_t vp9_decode(vpx_codec_alg_priv_t *ctx, 308 const uint8_t *data, 309 unsigned int data_sz, 310 void *user_priv, 311 long deadline) { 312 const uint8_t *data_start = data; 313 const uint8_t *data_end = data + data_sz; 314 vpx_codec_err_t res = VPX_CODEC_OK; 315 uint32_t sizes[8]; 316 int frames_this_pts, frame_count = 0; 317 318 if (data == NULL || data_sz == 0) return VPX_CODEC_INVALID_PARAM; 319 320 parse_superframe_index(data, data_sz, sizes, &frames_this_pts); 321 322 do { 323 // Skip over the superframe index, if present 324 if (data_sz && (*data_start & 0xe0) == 0xc0) { 325 const uint8_t marker = *data_start; 326 const uint32_t frames = (marker & 0x7) + 1; 327 const uint32_t mag = ((marker >> 3) & 0x3) + 1; 328 const uint32_t index_sz = 2 + mag * frames; 329 330 if (data_sz >= index_sz && data_start[index_sz - 1] == marker) { 331 data_start += index_sz; 332 data_sz -= index_sz; 333 if (data_start < data_end) 334 continue; 335 else 336 break; 337 } 338 } 339 340 // Use the correct size for this frame, if an index is present. 341 if (frames_this_pts) { 342 uint32_t this_sz = sizes[frame_count]; 343 344 if (data_sz < this_sz) { 345 ctx->base.err_detail = "Invalid frame size in index"; 346 return VPX_CODEC_CORRUPT_FRAME; 347 } 348 349 data_sz = this_sz; 350 frame_count++; 351 } 352 353 res = decode_one(ctx, &data_start, data_sz, user_priv, deadline); 354 assert(data_start >= data); 355 assert(data_start <= data_end); 356 357 /* Early exit if there was a decode error */ 358 if (res) 359 break; 360 361 /* Account for suboptimal termination by the encoder. */ 362 while (data_start < data_end && *data_start == 0) 363 data_start++; 364 365 data_sz = (unsigned int)(data_end - data_start); 366 } while (data_start < data_end); 367 return res; 368 } 369 370 static vpx_image_t *vp9_get_frame(vpx_codec_alg_priv_t *ctx, 371 vpx_codec_iter_t *iter) { 372 vpx_image_t *img = NULL; 373 374 if (ctx->img_avail) { 375 /* iter acts as a flip flop, so an image is only returned on the first 376 * call to get_frame. 377 */ 378 if (!(*iter)) { 379 img = &ctx->img; 380 *iter = img; 381 } 382 } 383 ctx->img_avail = 0; 384 385 return img; 386 } 387 388 static vpx_codec_err_t vp9_set_fb_fn( 389 vpx_codec_alg_priv_t *ctx, 390 vpx_get_frame_buffer_cb_fn_t cb_get, 391 vpx_release_frame_buffer_cb_fn_t cb_release, void *cb_priv) { 392 if (cb_get == NULL || cb_release == NULL) { 393 return VPX_CODEC_INVALID_PARAM; 394 } else if (ctx->pbi == NULL) { 395 // If the decoder has already been initialized, do not accept changes to 396 // the frame buffer functions. 397 ctx->get_ext_fb_cb = cb_get; 398 ctx->release_ext_fb_cb = cb_release; 399 ctx->ext_priv = cb_priv; 400 return VPX_CODEC_OK; 401 } 402 403 return VPX_CODEC_ERROR; 404 } 405 406 static vpx_codec_err_t set_reference(vpx_codec_alg_priv_t *ctx, int ctr_id, 407 va_list args) { 408 vpx_ref_frame_t *const data = va_arg(args, vpx_ref_frame_t *); 409 (void)ctr_id; 410 if (data) { 411 vpx_ref_frame_t *const frame = (vpx_ref_frame_t *)data; 412 YV12_BUFFER_CONFIG sd; 413 414 image2yuvconfig(&frame->img, &sd); 415 return vp9_set_reference_dec(&ctx->pbi->common, 416 (VP9_REFFRAME)frame->frame_type, &sd); 417 } else { 418 return VPX_CODEC_INVALID_PARAM; 419 } 420 } 421 422 static vpx_codec_err_t copy_reference(vpx_codec_alg_priv_t *ctx, int ctr_id, 423 va_list args) { 424 vpx_ref_frame_t *data = va_arg(args, vpx_ref_frame_t *); 425 (void)ctr_id; 426 if (data) { 427 vpx_ref_frame_t *frame = (vpx_ref_frame_t *)data; 428 YV12_BUFFER_CONFIG sd; 429 430 image2yuvconfig(&frame->img, &sd); 431 432 return vp9_copy_reference_dec(ctx->pbi, 433 (VP9_REFFRAME)frame->frame_type, &sd); 434 } else { 435 return VPX_CODEC_INVALID_PARAM; 436 } 437 } 438 439 static vpx_codec_err_t get_reference(vpx_codec_alg_priv_t *ctx, int ctr_id, 440 va_list args) { 441 vp9_ref_frame_t *data = va_arg(args, vp9_ref_frame_t *); 442 (void)ctr_id; 443 if (data) { 444 YV12_BUFFER_CONFIG* fb; 445 446 vp9_get_reference_dec(ctx->pbi, data->idx, &fb); 447 yuvconfig2image(&data->img, fb, NULL); 448 return VPX_CODEC_OK; 449 } else { 450 return VPX_CODEC_INVALID_PARAM; 451 } 452 } 453 454 static vpx_codec_err_t set_postproc(vpx_codec_alg_priv_t *ctx, int ctr_id, 455 va_list args) { 456 #if CONFIG_VP9_POSTPROC 457 vp8_postproc_cfg_t *data = va_arg(args, vp8_postproc_cfg_t *); 458 459 if (data) { 460 ctx->postproc_cfg_set = 1; 461 ctx->postproc_cfg = *((vp8_postproc_cfg_t *)data); 462 return VPX_CODEC_OK; 463 } else { 464 return VPX_CODEC_INVALID_PARAM; 465 } 466 #else 467 (void)ctr_id; 468 (void)ctx; 469 (void)args; 470 return VPX_CODEC_INCAPABLE; 471 #endif 472 } 473 474 static vpx_codec_err_t set_dbg_options(vpx_codec_alg_priv_t *ctx, int ctrl_id, 475 va_list args) { 476 #if CONFIG_POSTPROC_VISUALIZER && CONFIG_POSTPROC 477 int data = va_arg(args, int); 478 479 #define MAP(id, var) case id: var = data; break; 480 481 switch (ctrl_id) { 482 MAP(VP8_SET_DBG_COLOR_REF_FRAME, ctx->dbg_color_ref_frame_flag); 483 MAP(VP8_SET_DBG_COLOR_MB_MODES, ctx->dbg_color_mb_modes_flag); 484 MAP(VP8_SET_DBG_COLOR_B_MODES, ctx->dbg_color_b_modes_flag); 485 MAP(VP8_SET_DBG_DISPLAY_MV, ctx->dbg_display_mv_flag); 486 } 487 488 return VPX_CODEC_OK; 489 #else 490 (void)ctrl_id; 491 (void)ctx; 492 (void)args; 493 return VPX_CODEC_INCAPABLE; 494 #endif 495 } 496 497 static vpx_codec_err_t get_last_ref_updates(vpx_codec_alg_priv_t *ctx, 498 int ctrl_id, va_list args) { 499 int *const update_info = va_arg(args, int *); 500 501 (void)ctrl_id; 502 if (update_info) { 503 if (ctx->pbi) 504 *update_info = ctx->pbi->refresh_frame_flags; 505 else 506 return VPX_CODEC_ERROR; 507 return VPX_CODEC_OK; 508 } else { 509 return VPX_CODEC_INVALID_PARAM; 510 } 511 } 512 513 514 static vpx_codec_err_t get_frame_corrupted(vpx_codec_alg_priv_t *ctx, 515 int ctrl_id, va_list args) { 516 int *corrupted = va_arg(args, int *); 517 (void)ctrl_id; 518 519 if (corrupted) { 520 if (ctx->pbi) 521 *corrupted = ctx->pbi->common.frame_to_show->corrupted; 522 else 523 return VPX_CODEC_ERROR; 524 return VPX_CODEC_OK; 525 } else { 526 return VPX_CODEC_INVALID_PARAM; 527 } 528 } 529 530 static vpx_codec_err_t get_display_size(vpx_codec_alg_priv_t *ctx, 531 int ctrl_id, va_list args) { 532 int *const display_size = va_arg(args, int *); 533 (void)ctrl_id; 534 535 if (display_size) { 536 if (ctx->pbi) { 537 const VP9_COMMON *const cm = &ctx->pbi->common; 538 display_size[0] = cm->display_width; 539 display_size[1] = cm->display_height; 540 } else { 541 return VPX_CODEC_ERROR; 542 } 543 return VPX_CODEC_OK; 544 } else { 545 return VPX_CODEC_INVALID_PARAM; 546 } 547 } 548 549 static vpx_codec_err_t set_invert_tile_order(vpx_codec_alg_priv_t *ctx, 550 int ctrl_id, 551 va_list args) { 552 (void)ctrl_id; 553 (void)ctx; 554 ctx->invert_tile_order = va_arg(args, int); 555 return VPX_CODEC_OK; 556 } 557 558 static vpx_codec_ctrl_fn_map_t ctf_maps[] = { 559 {VP8_SET_REFERENCE, set_reference}, 560 {VP8_COPY_REFERENCE, copy_reference}, 561 {VP8_SET_POSTPROC, set_postproc}, 562 {VP8_SET_DBG_COLOR_REF_FRAME, set_dbg_options}, 563 {VP8_SET_DBG_COLOR_MB_MODES, set_dbg_options}, 564 {VP8_SET_DBG_COLOR_B_MODES, set_dbg_options}, 565 {VP8_SET_DBG_DISPLAY_MV, set_dbg_options}, 566 {VP8D_GET_LAST_REF_UPDATES, get_last_ref_updates}, 567 {VP8D_GET_FRAME_CORRUPTED, get_frame_corrupted}, 568 {VP9_GET_REFERENCE, get_reference}, 569 {VP9D_GET_DISPLAY_SIZE, get_display_size}, 570 {VP9_INVERT_TILE_DECODE_ORDER, set_invert_tile_order}, 571 { -1, NULL}, 572 }; 573 574 575 #ifndef VERSION_STRING 576 #define VERSION_STRING 577 #endif 578 CODEC_INTERFACE(vpx_codec_vp9_dx) = { 579 "WebM Project VP9 Decoder" VERSION_STRING, 580 VPX_CODEC_INTERNAL_ABI_VERSION, 581 VPX_CODEC_CAP_DECODER | VP9_CAP_POSTPROC | 582 VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER, 583 /* vpx_codec_caps_t caps; */ 584 vp9_init, /* vpx_codec_init_fn_t init; */ 585 vp9_destroy, /* vpx_codec_destroy_fn_t destroy; */ 586 ctf_maps, /* vpx_codec_ctrl_fn_map_t *ctrl_maps; */ 587 NOT_IMPLEMENTED, /* vpx_codec_get_mmap_fn_t get_mmap; */ 588 NOT_IMPLEMENTED, /* vpx_codec_set_mmap_fn_t set_mmap; */ 589 { // NOLINT 590 vp9_peek_si, /* vpx_codec_peek_si_fn_t peek_si; */ 591 vp9_get_si, /* vpx_codec_get_si_fn_t get_si; */ 592 vp9_decode, /* vpx_codec_decode_fn_t decode; */ 593 vp9_get_frame, /* vpx_codec_frame_get_fn_t frame_get; */ 594 vp9_set_fb_fn, /* vpx_codec_set_fb_fn_t set_fb_fn; */ 595 }, 596 { // NOLINT 597 /* encoder functions */ 598 NOT_IMPLEMENTED, 599 NOT_IMPLEMENTED, 600 NOT_IMPLEMENTED, 601 NOT_IMPLEMENTED, 602 NOT_IMPLEMENTED, 603 NOT_IMPLEMENTED, 604 NOT_IMPLEMENTED 605 } 606 }; 607