summaryrefslogtreecommitdiff
path: root/v4l2_vdin.cpp (plain)
blob: 393e1231ea0c7e17390a23e529541b742c209ddb
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18//reinclude because of a bug with the log macros
19//#define LOG_NDEBUG 0
20#define LOG_TAG "V4L2VINSOURCE"
21#include <utils/Log.h>
22#include <utils/String8.h>
23
24#include <signal.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <fcntl.h>
29#include <unistd.h>
30#include <errno.h>
31#include <sys/ioctl.h>
32#include <sys/mman.h>
33#include <sys/select.h>
34#include <linux/videodev.h>
35#include <sys/time.h>
36
37#include <cutils/properties.h>
38#include <sys/types.h>
39#include <sys/stat.h>
40
41#include "v4l2_vdin.h"
42#include <ui/GraphicBufferMapper.h>
43#include <ui/GraphicBuffer.h>
44#include <linux/videodev2.h>
45
46namespace android {
47
48#define V4L2_ROTATE_ID 0x980922
49
50#ifndef container_of
51#define container_of(ptr, type, member) ({ \
52 const typeof(((type *) 0)->member) *__mptr = (ptr); \
53 (type *) ((char *) __mptr - (char *)(&((type *)0)->member)); })
54#endif
55
56#define BOUNDRY 32
57
58#define ALIGN(x) (x + (BOUNDRY) - 1)& ~((BOUNDRY) - 1)
59
60static size_t getBufSize(int format, int width, int height)
61{
62 size_t buf_size = 0;
63
64 switch(format){
65 case V4L2_PIX_FMT_YVU420:
66 case V4L2_PIX_FMT_NV21:
67 buf_size = width * height * 3 / 2;
68 break;
69 case V4L2_PIX_FMT_YUYV:
70 case V4L2_PIX_FMT_RGB565:
71 buf_size = width * height * 2;
72 break;
73 case V4L2_PIX_FMT_RGB24:
74 buf_size = width * height * 3;
75 break;
76 case V4L2_PIX_FMT_RGB32:
77 buf_size = width * height * 4;
78 break;
79 default:
80 ALOGE("Invalid format");
81 buf_size = 0;
82 }
83 return buf_size;
84}
85
86static int getNativeWindowFormat(int format)
87{
88 int nativeFormat = HAL_PIXEL_FORMAT_YCbCr_422_I;
89
90 switch(format){
91 case V4L2_PIX_FMT_YVU420:
92 nativeFormat = HAL_PIXEL_FORMAT_YV12;
93 break;
94 case V4L2_PIX_FMT_NV21:
95 nativeFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP;
96 break;
97 case V4L2_PIX_FMT_YUYV:
98 nativeFormat = HAL_PIXEL_FORMAT_YCbCr_422_I;
99 break;
100 case V4L2_PIX_FMT_RGB565:
101 nativeFormat = HAL_PIXEL_FORMAT_RGB_565;
102 break;
103 case V4L2_PIX_FMT_RGB24:
104 nativeFormat = HAL_PIXEL_FORMAT_RGB_888;
105 break;
106 case V4L2_PIX_FMT_RGB32:
107 nativeFormat = HAL_PIXEL_FORMAT_RGBA_8888;
108 break;
109 default:
110 ALOGE("Invalid format,Use default format");
111 }
112 return nativeFormat;
113}
114
115
116static ANativeWindowBuffer* handle_to_buffer(buffer_handle_t *handle)
117{
118 return container_of(handle, ANativeWindowBuffer, handle);
119}
120
121vdin_screen_source::vdin_screen_source()
122 : mCameraHandle(-1),
123 mVideoInfo(NULL)
124{
125 mCameraHandle = open("/dev/video11", O_RDWR| O_NONBLOCK);
126 if (mCameraHandle < 0){
127 ALOGE("[%s %d] mCameraHandle:%x", __FUNCTION__, __LINE__, mCameraHandle);
128 }
129 mVideoInfo = (struct VideoInfo *) calloc (1, sizeof (struct VideoInfo));
130 if (mVideoInfo == NULL){
131 ALOGE("[%s %d] no memory for mVideoInfo", __FUNCTION__, __LINE__);
132 }
133 mBufferCount = 4;
134 mPixelFormat = V4L2_PIX_FMT_NV21;
135 mNativeWindowPixelFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP;
136 mFrameWidth = 1280;
137 mFrameHeight = 720;
138 mBufferSize = mFrameWidth * mFrameHeight * 3/2;
139 mSetStateCB = NULL;
140 mState = STOP;
141 mANativeWindow = NULL;
142 mFrameType = 0;
143 mWorkThread = NULL;
144 mDataCB = NULL;
145 mOpen = false;
146}
147
148vdin_screen_source::~vdin_screen_source()
149{
150 if (mVideoInfo)
151 free (mVideoInfo);
152 if (mCameraHandle >= 0)
153 close(mCameraHandle);
154}
155
156int vdin_screen_source::start_v4l2_device()
157{
158 int ret = -1;
159
160 ALOGV("[%s %d] mCameraHandle:%x", __FUNCTION__, __LINE__, mCameraHandle);
161
162 ioctl(mCameraHandle, VIDIOC_QUERYCAP, &mVideoInfo->cap);
163
164 mVideoInfo->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
165 mVideoInfo->rb.memory = V4L2_MEMORY_MMAP;
166 mVideoInfo->rb.count = mBufferCount;
167
168 ret = ioctl(mCameraHandle, VIDIOC_REQBUFS, &mVideoInfo->rb);
169
170 if (ret < 0) {
171 ALOGE("[%s %d] VIDIOC_REQBUFS:%d mCameraHandle:%x", __FUNCTION__, __LINE__, ret, mCameraHandle);
172 return ret;
173 }
174
175 for (int i = 0; i < mBufferCount; i++) {
176 memset (&mVideoInfo->buf, 0, sizeof (struct v4l2_buffer));
177
178 mVideoInfo->buf.index = i;
179 mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
180 mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
181
182 ret = ioctl (mCameraHandle, VIDIOC_QUERYBUF, &mVideoInfo->buf);
183 if (ret < 0) {
184 ALOGE("[%s %d]VIDIOC_QUERYBUF %d failed", __FUNCTION__, __LINE__, i);
185 return ret;
186 }
187 mVideoInfo->canvas[i] = mVideoInfo->buf.reserved;
188 mVideoInfo->mem[i] = mmap (0, mVideoInfo->buf.length, PROT_READ | PROT_WRITE,
189 MAP_SHARED, mCameraHandle, mVideoInfo->buf.m.offset);
190
191 if (mVideoInfo->mem[i] == MAP_FAILED) {
192 ALOGE("[%s %d] MAP_FAILED", __FUNCTION__, __LINE__);
193 return -1;
194 }
195 mVideoInfo->refcount[i] = 0;
196 mBufs.add((int)mVideoInfo->mem[i],i);
197 }
198 ALOGV("[%s %d] VIDIOC_QUERYBUF successful", __FUNCTION__, __LINE__);
199
200 for (int i = 0; i < mBufferCount; i++) {
201 mVideoInfo->buf.index = i;
202 mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
203 mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
204 ret = ioctl(mCameraHandle, VIDIOC_QBUF, &mVideoInfo->buf);
205 if (ret < 0) {
206 ALOGE("VIDIOC_QBUF Failed");
207 return -1;
208 }
209 }
210 enum v4l2_buf_type bufType;
211 bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
212
213 ret = ioctl (mCameraHandle, VIDIOC_STREAMON, &bufType);
214
215 ALOGV("[%s %d] VIDIOC_STREAMON:%x", __FUNCTION__, __LINE__, ret);
216 return ret;
217}
218
219int vdin_screen_source::start()
220{
221 ALOGV("%s %d", __FUNCTION__, __LINE__);
222 int ret;
223 if(mOpen == true){
224 ALOGI("already open");
225 return NO_ERROR;
226 }
227
228 ret = start_v4l2_device();
229 if(ret != NO_ERROR){
230 ALOGE("Start v4l2 device failed:%d",ret);
231 return ret;
232 }
233 if(mFrameType & NATIVE_WINDOW_DATA){
234 ret = init_native_window();
235 if(ret != NO_ERROR){
236 ALOGE("Init Native Window Failed:%d",ret);
237 return ret;
238 }
239 }
240 if(mFrameType & NATIVE_WINDOW_DATA || mFrameType & CALL_BACK_DATA){
241 ALOGD("Create Work Thread");
242 mWorkThread = new WorkThread(this);
243 }
244 if(mSetStateCB != NULL)
245 mSetStateCB(START);
246 mState = START;
247 mOpen = true;
248 ALOGV("%s %d ret:%d", __FUNCTION__, __LINE__, ret);
249 return NO_ERROR;
250}
251
252int vdin_screen_source::pause()
253{
254 ALOGV("%s %d", __FUNCTION__, __LINE__);
255 mState = PAUSE;
256 if(mSetStateCB != NULL)
257 mSetStateCB(PAUSE);
258 return NO_ERROR;
259}
260int vdin_screen_source::stop()
261{
262 ALOGV("%s %d", __FUNCTION__, __LINE__);
263 int ret;
264 mState = STOPING;
265
266 if(mWorkThread != NULL){
267 mWorkThread->requestExitAndWait();
268 mWorkThread.clear();
269 }
270
271 enum v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
272
273 ret = ioctl (mCameraHandle, VIDIOC_STREAMOFF, &bufType);
274 if (ret < 0) {
275 ALOGE("StopStreaming: Unable to stop capture: %s", strerror(errno));
276 }
277 for (int i = 0; i < mBufferCount; i++){
278 if (munmap(mVideoInfo->mem[i], mVideoInfo->buf.length) < 0)
279 ALOGE("Unmap failed");
280 }
281
282 mBufferCount = 0;
283 mState = STOP;
284 if(mSetStateCB != NULL)
285 mSetStateCB(STOP);
286 mOpen = false;
287 return ret;
288}
289
290int vdin_screen_source::set_state_callback(olStateCB callback)
291{
292 if (!callback){
293 ALOGE("NULL state callback pointer");
294 return BAD_VALUE;
295 }
296 mSetStateCB = callback;
297 return NO_ERROR;
298}
299
300int vdin_screen_source::set_preview_window(ANativeWindow* window)
301{
302 ALOGV("%s %d", __FUNCTION__, __LINE__);
303 if(mOpen == true)
304 return NO_ERROR;
305 //can work without a valid window object ?
306 if (window == NULL){
307 ALOGD("NULL window object passed to ScreenSource");
308 if(mWorkThread != NULL){
309 mWorkThread->requestExitAndWait();
310 mWorkThread.clear();
311 }
312 mFrameType &= ~NATIVE_WINDOW_DATA;
313 return NO_ERROR;
314 }
315 mFrameType |= NATIVE_WINDOW_DATA;
316 mANativeWindow = window;
317 return NO_ERROR;
318}
319
320int vdin_screen_source::set_data_callback(app_data_callback callback, void* user)
321{
322 ALOGV("%s %d", __FUNCTION__, __LINE__);
323 if (callback == NULL){
324 ALOGE("NULL data callback pointer");
325 return BAD_VALUE;
326 }
327 mDataCB = callback;
328 mUser = user;
329 mFrameType |= CALL_BACK_DATA;
330 return NO_ERROR;
331}
332
333int vdin_screen_source::get_format()
334{
335 return mPixelFormat;
336}
337
338int vdin_screen_source::set_format(int width, int height, int color_format)
339{
340 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
341 if(mOpen == true)
342 return NO_ERROR;
343 int ret;
344 mVideoInfo->width = ALIGN(width);
345 mVideoInfo->height = height;
346 mVideoInfo->framesizeIn = (mVideoInfo->width * mVideoInfo->height << 3); //note color format
347 mVideoInfo->formatIn = color_format;
348
349 mVideoInfo->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
350 mVideoInfo->format.fmt.pix.width = ALIGN(width);
351 mVideoInfo->format.fmt.pix.height = height;
352 mVideoInfo->format.fmt.pix.pixelformat = color_format;
353 mPixelFormat = color_format;
354 mNativeWindowPixelFormat = getNativeWindowFormat(color_format);
355 mFrameWidth = ALIGN(width);
356 mFrameHeight = height;
357 mBufferSize = getBufSize(color_format, mFrameWidth, mFrameHeight);
358 ALOGD("mFrameWidth:%d,mFrameHeight:%d",mFrameWidth,mFrameHeight);
359 ALOGD("mPixelFormat:%x,mNativeWindowPixelFormat:%x,mBufferSize:%d",mPixelFormat,mNativeWindowPixelFormat,mBufferSize);
360 ret = ioctl(mCameraHandle, VIDIOC_S_FMT, &mVideoInfo->format);
361 if (ret < 0) {
362 ALOGE("[%s %d]VIDIOC_S_FMT %d", __FUNCTION__, __LINE__, ret);
363 return ret;
364 }
365 return ret;
366}
367
368int vdin_screen_source::set_rotation(int degree)
369{
370 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
371
372 int ret = 0;
373 struct v4l2_control ctl;
374
375 if(mCameraHandle<0)
376 return -1;
377
378 if((degree!=0)&&(degree!=90)&&(degree!=180)&&(degree!=270)){
379 ALOGE("Set rotate value invalid: %d.", degree);
380 return -1;
381 }
382
383 memset( &ctl, 0, sizeof(ctl));
384 ctl.value=degree;
385 ctl.id = V4L2_ROTATE_ID;
386 ret = ioctl(mCameraHandle, VIDIOC_S_CTRL, &ctl);
387
388 if(ret<0){
389 ALOGE("Set rotate value fail: %s. ret=%d", strerror(errno),ret);
390 }
391 return ret ;
392}
393
394int vdin_screen_source::set_crop(int x, int y, int width, int height)
395{
396 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
397 if (NULL == mANativeWindow.get())
398 return BAD_VALUE;
399
400 int err = NO_ERROR;
401 android_native_rect_t crop = { x, y, x + width - 1, y + height - 1 };
402 err = native_window_set_crop(mANativeWindow.get(), &crop);
403 if (err != 0) {
404 ALOGW("Failed to set crop!");
405 return err;
406 }
407 return NO_ERROR;
408}
409
410int vdin_screen_source::set_frame_rate(int frameRate)
411{
412 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
413 int ret = 0;
414 struct v4l2_control ctl;
415
416 if(mCameraHandle<0)
417 return -1;
418
419 struct v4l2_streamparm sparm;
420 memset(&sparm, 0, sizeof( sparm ));
421 sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//stream_flag;
422 sparm.parm.output.timeperframe.denominator = frameRate;
423 sparm.parm.output.timeperframe.numerator = 1;
424
425 ret = ioctl(mCameraHandle, VIDIOC_S_PARM, &sparm);
426 if(ret < 0){
427 ALOGE("Set frame rate fail: %s. ret=%d", strerror(errno),ret);
428 }
429 return ret ;
430}
431
432int vdin_screen_source::set_source_type(int sourceType)
433{
434 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
435 int ret = 0;
436
437 ret = ioctl(mCameraHandle, VIDIOC_S_INPUT, &sourceType);
438 if(ret < 0){
439 ALOGE("Set source type fail: %s. ret:%d", strerror(errno),ret);
440 }
441 return ret;
442}
443
444int vdin_screen_source::get_source_type()
445{
446 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
447 int ret = -1;
448 int sourceType;
449
450 ret = ioctl(mCameraHandle, VIDIOC_G_INPUT, &sourceType);
451 if(ret < 0){
452 ALOGE("Set source type fail: %s. ret:%d", strerror(errno),ret);
453 return ret;
454 }
455 return sourceType;
456}
457
458int vdin_screen_source::aquire_buffer(int *buff_info)
459{
460 ALOGV("%s %d", __FUNCTION__, __LINE__);
461 int ret = -1;
462 mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
463 mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
464
465 ret = ioctl(mCameraHandle, VIDIOC_DQBUF, &mVideoInfo->buf);
466 if (ret < 0) {
467 if(EAGAIN == errno){
468 ret = -EAGAIN;
469 }else{
470 ALOGE("[%s %d]aquire_buffer %d", __FUNCTION__, __LINE__, ret);
471 }
472 buff_info[0] = 0;
473 buff_info[1] = 0;
474 return ret;
475 }
476 buff_info[0] = (unsigned)mVideoInfo->mem[mVideoInfo->buf.index];
477 buff_info[1] = (unsigned)mVideoInfo->canvas[mVideoInfo->buf.index];
478 buff_info[2] = mVideoInfo->buf.timestamp.tv_sec;
479 buff_info[3] = mVideoInfo->buf.timestamp.tv_usec;
480 return ret;
481}
482
483/* int vdin_screen_source::inc_buffer_refcount(int *ptr){
484 ALOGV("%s %d", __FUNCTION__, __LINE__);
485 int ret = -1;
486 int index;
487 index = mBufs.valueFor((unsigned int)ptr);
488 mVideoInfo->refcount[index] += 1;
489 return true;
490} */
491
492int vdin_screen_source::release_buffer(int* ptr)
493{
494 ALOGV("%s %d", __FUNCTION__, __LINE__);
495 int ret = -1;
496 int currentIndex;
497 v4l2_buffer hbuf_query;
498
499 Mutex::Autolock autoLock(mLock);
500
501 currentIndex = mBufs.valueFor((unsigned int)ptr);
502 if(mVideoInfo->refcount[currentIndex] > 0){
503 mVideoInfo->refcount[currentIndex] -= 1;
504 }else{
505 ALOGE("return buffer when refcount already zero");
506 return 0;
507 }
508 if(mVideoInfo->refcount[currentIndex] == 0){
509 memset(&hbuf_query,0,sizeof(v4l2_buffer));
510 hbuf_query.index = currentIndex;
511 hbuf_query.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
512 hbuf_query.memory = V4L2_MEMORY_MMAP;
513 ALOGV("return buffer :%d",currentIndex);
514 ret = ioctl(mCameraHandle, VIDIOC_QBUF, &hbuf_query);
515 if(ret != 0){
516 ALOGE("Return Buffer :%d failed", currentIndex);
517 }
518 }
519 return 0;
520}
521
522int vdin_screen_source::init_native_window()
523{
524 ALOGV("%s %d", __FUNCTION__, __LINE__);
525 int err = NO_ERROR;
526
527 if(NULL == mANativeWindow.get())
528 return BAD_VALUE;
529
530 // Set gralloc usage bits for window.
531 err = native_window_set_usage(mANativeWindow.get(), SCREENSOURCE_GRALLOC_USAGE);
532 if (err != 0) {
533 ALOGE("native_window_set_usage failed: %s\n", strerror(-err));
534 if(ENODEV == err ){
535 ALOGE("Preview surface abandoned!");
536 mANativeWindow = NULL;
537 }
538 return err;
539 }
540
541 ALOGD("Number of buffers set to ANativeWindow %d", mBufferCount);
542 ///Set the number of buffers needed for camera preview
543 err = native_window_set_buffer_count(mANativeWindow.get(), mBufferCount);
544 if (err != 0) {
545 ALOGE("native_window_set_buffer_count failed: %s (%d)", strerror(-err), -err);
546 if(ENODEV == err){
547 ALOGE("Preview surface abandoned!");
548 mANativeWindow = NULL;
549 }
550 return err;
551 }
552
553 ALOGD("native_window_set_buffers_geometry format:0x%x",mNativeWindowPixelFormat);
554 // Set window geometry
555 err = native_window_set_buffers_geometry(
556 mANativeWindow.get(),
557 mFrameWidth,
558 mFrameHeight,
559 mNativeWindowPixelFormat);
560
561 if (err != 0) {
562 ALOGE("native_window_set_buffers_geometry failed: %s", strerror(-err));
563 if ( ENODEV == err ) {
564 ALOGE("Surface abandoned!");
565 mANativeWindow = NULL;
566 }
567 return err;
568 }
569 err = native_window_set_scaling_mode(mANativeWindow.get(), NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
570 if (err != 0) {
571 ALOGW("Failed to set scaling mode: %d", err);
572 return err;
573 }
574 return NO_ERROR;
575}
576
577int vdin_screen_source::workThread()
578{
579 bool buff_keep = false;
580 int index;
581 int buff_info[4], buff_info_latest[4];
582 int ret;
583 unsigned char *src = NULL;
584 unsigned char *dest = NULL;
585 uint8_t *handle = NULL;
586 ANativeWindowBuffer* buf;
587 if(mState == START){
588 usleep(5000);
589#if 0
590 ret = aquire_buffer(buff_info);
591#else
592 while(mState == START){
593 ret = aquire_buffer(buff_info);
594 if(ret != 0){
595 if(true == buff_keep){//use the latest buffer
596 memcpy(buff_info, buff_info_latest, 16);
597 ret = 0;
598 }
599 break;
600 }else{
601 if(true == buff_keep){
602 //release
603 ALOGD("v4l2 vdin drop frame");
604 release_buffer((int *)(buff_info_latest[0]));
605 }
606 buff_keep = true;
607 memcpy(buff_info_latest, buff_info, 16);
608 }
609 }
610#endif
611 if(ret != 0 || (buff_info[0] == 0)){
612 ALOGV("Get V4l2 buffer failed");
613 return ret;
614 }
615 src = (unsigned char *)buff_info[0];
616 index = mBufs.valueFor((unsigned int)src);
617 if(mFrameType & NATIVE_WINDOW_DATA){
618 mVideoInfo->refcount[index] += 1;
619 if(mANativeWindow.get() == NULL){
620 ALOGE("Null window");
621 return BAD_VALUE;
622 }
623 ret = mANativeWindow->dequeueBuffer_DEPRECATED(mANativeWindow.get(), &buf);
624 if(ret != 0){
625 ALOGE("dequeue buffer failed :%s (%d)",strerror(-ret), -ret);
626 return BAD_VALUE;
627 }
628 mANativeWindow->lockBuffer_DEPRECATED(mANativeWindow.get(), buf);
629 sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(buf, false));
630 graphicBuffer->lock(SCREENSOURCE_GRALLOC_USAGE, (void **)&dest);
631 if(dest == NULL){
632 ALOGE("Invalid Gralloc Handle");
633 return BAD_VALUE;
634 }
635 memcpy(dest, src, mBufferSize);
636 graphicBuffer->unlock();
637 mANativeWindow->queueBuffer_DEPRECATED(mANativeWindow.get(), buf);
638 graphicBuffer.clear();
639 ALOGV("queue one buffer to native window");
640 release_buffer((int*)src);
641 }
642 if(mFrameType & CALL_BACK_DATA && mDataCB != NULL&& mState == START){
643 mVideoInfo->refcount[index] += 1;
644 mDataCB(mUser, buff_info);
645 }
646 }
647 return NO_ERROR;
648}
649
650}
651