ProCameraTests.cpp revision 5076182ce4bf657e7211264d0ad3861212f24aa1
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#include <gtest/gtest.h> 18#include <iostream> 19 20#include <binder/IPCThreadState.h> 21#include <utils/Thread.h> 22 23#include "Camera.h" 24#include "ProCamera.h" 25#include <utils/Vector.h> 26#include <utils/Mutex.h> 27#include <utils/Condition.h> 28 29#include <gui/SurfaceComposerClient.h> 30#include <gui/Surface.h> 31 32#include <system/camera_metadata.h> 33#include <hardware/camera2.h> // for CAMERA2_TEMPLATE_PREVIEW only 34 35namespace android { 36namespace camera2 { 37namespace tests { 38namespace client { 39 40#define CAMERA_ID 0 41#define TEST_DEBUGGING 0 42 43#define TEST_LISTENER_TIMEOUT 1000000000 // 1 second listener timeout 44#define TEST_FORMAT HAL_PIXEL_FORMAT_Y16 //TODO: YUY2 instead 45 46#define TEST_FORMAT_MAIN HAL_PIXEL_FORMAT_Y8 47#define TEST_FORMAT_DEPTH HAL_PIXEL_FORMAT_Y16 48 49#define TEST_CPU_FRAME_COUNT 2 50#define TEST_CPU_HEAP_COUNT 5 51 52#if TEST_DEBUGGING 53#define dout std::cerr 54#else 55#define dout if (0) std::cerr 56#endif 57 58#define EXPECT_OK(x) EXPECT_EQ(OK, (x)) 59#define ASSERT_OK(x) ASSERT_EQ(OK, (x)) 60 61class ProCameraTest; 62 63enum ProEvent { 64 UNKNOWN, 65 ACQUIRED, 66 RELEASED, 67 STOLEN, 68 BUFFER_RECEIVED, 69}; 70 71typedef Vector<ProEvent> EventList; 72 73class ProCameraTestThread : public Thread 74{ 75public: 76 ProCameraTestThread() { 77 } 78 79 virtual bool threadLoop() { 80 mProc = ProcessState::self(); 81 mProc->startThreadPool(); 82 83 IPCThreadState *ptr = IPCThreadState::self(); 84 85 ptr->joinThreadPool(); 86 87 return false; 88 } 89 90 sp<ProcessState> mProc; 91}; 92 93class ProCameraTestListener : public ProCameraListener { 94 95public: 96 status_t WaitForEvent() { 97 Mutex::Autolock cal(mConditionMutex); 98 99 { 100 Mutex::Autolock al(mListenerMutex); 101 102 if (mProEventList.size() > 0) { 103 return OK; 104 } 105 } 106 107 return mListenerCondition.waitRelative(mConditionMutex, 108 TEST_LISTENER_TIMEOUT); 109 } 110 111 /* Read events into out. Existing queue is flushed */ 112 void ReadEvents(EventList& out) { 113 Mutex::Autolock al(mListenerMutex); 114 115 for (size_t i = 0; i < mProEventList.size(); ++i) { 116 out.push(mProEventList[i]); 117 } 118 119 mProEventList.clear(); 120 } 121 122 /** 123 * Dequeue 1 event from the event queue. 124 * Returns UNKNOWN if queue is empty 125 */ 126 ProEvent ReadEvent() { 127 Mutex::Autolock al(mListenerMutex); 128 129 if (mProEventList.size() == 0) { 130 return UNKNOWN; 131 } 132 133 ProEvent ev = mProEventList[0]; 134 mProEventList.removeAt(0); 135 136 return ev; 137 } 138 139private: 140 void QueueEvent(ProEvent ev) { 141 { 142 Mutex::Autolock al(mListenerMutex); 143 mProEventList.push(ev); 144 } 145 146 147 mListenerCondition.broadcast(); 148 } 149 150protected: 151 152 ////////////////////////////////////////////////// 153 ///////// ProCameraListener ////////////////////// 154 ////////////////////////////////////////////////// 155 156 157 // Lock has been acquired. Write operations now available. 158 virtual void onLockAcquired() { 159 QueueEvent(ACQUIRED); 160 } 161 // Lock has been released with exclusiveUnlock 162 virtual void onLockReleased() { 163 QueueEvent(RELEASED); 164 } 165 166 // Lock has been stolen by another client. 167 virtual void onLockStolen() { 168 QueueEvent(STOLEN); 169 } 170 171 // Lock free. 172 virtual void onTriggerNotify(int32_t ext1, int32_t ext2, int32_t ext3) { 173 174 dout << "Trigger notify: " << ext1 << " " << ext2 175 << " " << ext3 << std::endl; 176 } 177 178 virtual void onBufferReceived(int streamId, 179 const CpuConsumer::LockedBuffer& buf) { 180 181 dout << "Buffer received on streamId = " << streamId << 182 ", dataPtr = " << (void*)buf.data << std::endl; 183 184 QueueEvent(BUFFER_RECEIVED); 185 186 } 187 virtual void onRequestReceived( 188 camera_metadata* request) { 189 free_camera_metadata(request); 190 } 191 192 // TODO: remove 193 194 virtual void notify(int32_t , int32_t , int32_t ) {} 195 virtual void postData(int32_t , const sp<IMemory>& , 196 camera_frame_metadata_t *) {} 197 virtual void postDataTimestamp(nsecs_t , int32_t , const sp<IMemory>& ) {} 198 199 200 Vector<ProEvent> mProEventList; 201 Mutex mListenerMutex; 202 Mutex mConditionMutex; 203 Condition mListenerCondition; 204}; 205 206class ProCameraTest : public ::testing::Test { 207 208public: 209 ProCameraTest() { 210 } 211 212 static void SetUpTestCase() { 213 // Binder Thread Pool Initialization 214 mTestThread = new ProCameraTestThread(); 215 mTestThread->run("ProCameraTestThread"); 216 } 217 218 virtual void SetUp() { 219 mCamera = ProCamera::connect(CAMERA_ID); 220 ASSERT_NE((void*)NULL, mCamera.get()); 221 222 mListener = new ProCameraTestListener(); 223 mCamera->setListener(mListener); 224 } 225 226 virtual void TearDown() { 227 ASSERT_NE((void*)NULL, mCamera.get()); 228 mCamera->disconnect(); 229 } 230 231protected: 232 sp<ProCamera> mCamera; 233 sp<ProCameraTestListener> mListener; 234 235 static sp<Thread> mTestThread; 236 237 int mDisplaySecs; 238 sp<SurfaceComposerClient> mComposerClient; 239 sp<SurfaceControl> mSurfaceControl; 240 241 sp<SurfaceComposerClient> mDepthComposerClient; 242 sp<SurfaceControl> mDepthSurfaceControl; 243 244 int getSurfaceWidth() { 245 return 512; 246 } 247 int getSurfaceHeight() { 248 return 512; 249 } 250 251 void createOnScreenSurface(sp<Surface>& surface) { 252 mComposerClient = new SurfaceComposerClient; 253 ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); 254 255 mSurfaceControl = mComposerClient->createSurface( 256 String8("ProCameraTest StreamingImage Surface"), 257 getSurfaceWidth(), getSurfaceHeight(), 258 PIXEL_FORMAT_RGB_888, 0); 259 260 mSurfaceControl->setPosition(640, 0); 261 262 ASSERT_TRUE(mSurfaceControl != NULL); 263 ASSERT_TRUE(mSurfaceControl->isValid()); 264 265 SurfaceComposerClient::openGlobalTransaction(); 266 ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF)); 267 ASSERT_EQ(NO_ERROR, mSurfaceControl->show()); 268 SurfaceComposerClient::closeGlobalTransaction(); 269 270 sp<ANativeWindow> window = mSurfaceControl->getSurface(); 271 surface = mSurfaceControl->getSurface(); 272 273 ASSERT_NE((void*)NULL, surface.get()); 274 } 275 276 void createDepthOnScreenSurface(sp<Surface>& surface) { 277 mDepthComposerClient = new SurfaceComposerClient; 278 ASSERT_EQ(NO_ERROR, mDepthComposerClient->initCheck()); 279 280 mDepthSurfaceControl = mDepthComposerClient->createSurface( 281 String8("ProCameraTest StreamingImage Surface"), 282 getSurfaceWidth(), getSurfaceHeight(), 283 PIXEL_FORMAT_RGB_888, 0); 284 285 mDepthSurfaceControl->setPosition(640, 0); 286 287 ASSERT_TRUE(mDepthSurfaceControl != NULL); 288 ASSERT_TRUE(mDepthSurfaceControl->isValid()); 289 290 SurfaceComposerClient::openGlobalTransaction(); 291 ASSERT_EQ(NO_ERROR, mDepthSurfaceControl->setLayer(0x7FFFFFFF)); 292 ASSERT_EQ(NO_ERROR, mDepthSurfaceControl->show()); 293 SurfaceComposerClient::closeGlobalTransaction(); 294 295 sp<ANativeWindow> window = mDepthSurfaceControl->getSurface(); 296 surface = mDepthSurfaceControl->getSurface(); 297 298 ASSERT_NE((void*)NULL, surface.get()); 299 } 300 301}; 302 303sp<Thread> ProCameraTest::mTestThread; 304 305// test around exclusiveTryLock (immediate locking) 306TEST_F(ProCameraTest, LockingImmediate) { 307 308 if (HasFatalFailure()) { 309 return; 310 } 311 312 EXPECT_FALSE(mCamera->hasExclusiveLock()); 313 EXPECT_EQ(OK, mCamera->exclusiveTryLock()); 314 // at this point we definitely have the lock 315 316 EXPECT_EQ(OK, mListener->WaitForEvent()); 317 EXPECT_EQ(ACQUIRED, mListener->ReadEvent()); 318 319 EXPECT_TRUE(mCamera->hasExclusiveLock()); 320 EXPECT_EQ(OK, mCamera->exclusiveUnlock()); 321 322 EXPECT_EQ(OK, mListener->WaitForEvent()); 323 EXPECT_EQ(RELEASED, mListener->ReadEvent()); 324 325 EXPECT_FALSE(mCamera->hasExclusiveLock()); 326} 327 328// test around exclusiveLock (locking at some future point in time) 329TEST_F(ProCameraTest, LockingAsynchronous) { 330 331 if (HasFatalFailure()) { 332 return; 333 } 334 335 // TODO: Add another procamera that has a lock here. 336 // then we can be test that the lock wont immediately be acquired 337 338 EXPECT_FALSE(mCamera->hasExclusiveLock()); 339 EXPECT_EQ(OK, mCamera->exclusiveLock()); 340 // at this point we may or may not have the lock 341 // we cant be sure until we get an ACQUIRED event 342 343 EXPECT_EQ(OK, mListener->WaitForEvent()); 344 EXPECT_EQ(ACQUIRED, mListener->ReadEvent()); 345 346 EXPECT_TRUE(mCamera->hasExclusiveLock()); 347 EXPECT_EQ(OK, mCamera->exclusiveUnlock()); 348 349 EXPECT_EQ(OK, mListener->WaitForEvent()); 350 EXPECT_EQ(RELEASED, mListener->ReadEvent()); 351 352 EXPECT_FALSE(mCamera->hasExclusiveLock()); 353} 354 355// Stream directly to the screen. 356TEST_F(ProCameraTest, StreamingImageSingle) { 357 if (HasFatalFailure()) { 358 return; 359 } 360 char* displaySecsEnv = getenv("TEST_DISPLAY_SECS"); 361 if (displaySecsEnv != NULL) { 362 mDisplaySecs = atoi(displaySecsEnv); 363 if (mDisplaySecs < 0) { 364 mDisplaySecs = 0; 365 } 366 } else { 367 mDisplaySecs = 0; 368 } 369 370 sp<Surface> depthSurface; 371 if (mDisplaySecs > 0) { 372 createDepthOnScreenSurface(/*out*/depthSurface); 373 } 374 375 int depthStreamId = -1; 376 EXPECT_OK(mCamera->createStream(/*width*/320, /*height*/240, 377 TEST_FORMAT_DEPTH, depthSurface, &depthStreamId)); 378 EXPECT_NE(-1, depthStreamId); 379 380 EXPECT_OK(mCamera->exclusiveTryLock()); 381 /* iterate in a loop submitting requests every frame. 382 * what kind of requests doesnt really matter, just whatever. 383 */ 384 385 // it would probably be better to use CameraMetadata from camera service. 386 camera_metadata_t *request = NULL; 387 EXPECT_OK(mCamera->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW, 388 /*out*/&request)); 389 EXPECT_NE((void*)NULL, request); 390 391 /* FIXME: dont need this later, at which point the above should become an 392 ASSERT_NE*/ 393 if(request == NULL) request = allocate_camera_metadata(10, 100); 394 395 // set the output streams to just this stream ID 396 397 // wow what a verbose API. 398 // i would give a loaf of bread for 399 // metadata->updateOrInsert(keys.request.output.streams, streamId); 400 uint8_t allStreams[] = { depthStreamId }; 401 size_t streamCount = sizeof(allStreams) / sizeof(allStreams[0]); 402 403 camera_metadata_entry_t entry; 404 uint32_t tag = static_cast<uint32_t>(ANDROID_REQUEST_OUTPUT_STREAMS); 405 int find = find_camera_metadata_entry(request, tag, &entry); 406 if (find == -ENOENT) { 407 if (add_camera_metadata_entry(request, tag, &allStreams, 408 /*data_count*/streamCount) != OK) { 409 camera_metadata_t *tmp = allocate_camera_metadata(1000, 10000); 410 ASSERT_OK(append_camera_metadata(tmp, request)); 411 free_camera_metadata(request); 412 request = tmp; 413 414 ASSERT_OK(add_camera_metadata_entry(request, tag, &allStreams, 415 /*data_count*/streamCount)); 416 } 417 } else { 418 ASSERT_OK(update_camera_metadata_entry(request, entry.index, 419 &allStreams, /*data_count*/streamCount, &entry)); 420 } 421 422 EXPECT_OK(mCamera->submitRequest(request, /*streaming*/true)); 423 424 dout << "will sleep now for " << mDisplaySecs << std::endl; 425 sleep(mDisplaySecs); 426 427 free_camera_metadata(request); 428 429 for (int i = 0; i < streamCount; ++i) { 430 EXPECT_OK(mCamera->deleteStream(allStreams[i])); 431 } 432 EXPECT_OK(mCamera->exclusiveUnlock()); 433} 434 435// Stream directly to the screen. 436TEST_F(ProCameraTest, StreamingImageDual) { 437 if (HasFatalFailure()) { 438 return; 439 } 440 char* displaySecsEnv = getenv("TEST_DISPLAY_SECS"); 441 if (displaySecsEnv != NULL) { 442 mDisplaySecs = atoi(displaySecsEnv); 443 if (mDisplaySecs < 0) { 444 mDisplaySecs = 0; 445 } 446 } else { 447 mDisplaySecs = 0; 448 } 449 450 sp<Surface> surface; 451 sp<Surface> depthSurface; 452 if (mDisplaySecs > 0) { 453 createOnScreenSurface(/*out*/surface); 454 createDepthOnScreenSurface(/*out*/depthSurface); 455 } 456 457 int streamId = -1; 458 EXPECT_OK(mCamera->createStream(/*width*/1280, /*height*/960, 459 TEST_FORMAT_MAIN, surface, &streamId)); 460 EXPECT_NE(-1, streamId); 461 462 int depthStreamId = -1; 463 EXPECT_OK(mCamera->createStream(/*width*/320, /*height*/240, 464 TEST_FORMAT_DEPTH, depthSurface, &depthStreamId)); 465 EXPECT_NE(-1, depthStreamId); 466 467 EXPECT_OK(mCamera->exclusiveTryLock()); 468 /* 469 */ 470 /* iterate in a loop submitting requests every frame. 471 * what kind of requests doesnt really matter, just whatever. 472 */ 473 474 // it would probably be better to use CameraMetadata from camera service. 475 camera_metadata_t *request = NULL; 476 EXPECT_OK(mCamera->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW, 477 /*out*/&request)); 478 EXPECT_NE((void*)NULL, request); 479 480 /*FIXME: dont need this later, at which point the above should become an 481 ASSERT_NE*/ 482 if(request == NULL) request = allocate_camera_metadata(10, 100); 483 484 // set the output streams to just this stream ID 485 486 // wow what a verbose API. 487 uint8_t allStreams[] = { streamId, depthStreamId }; 488 // IMPORTANT. bad things will happen if its not a uint8. 489 size_t streamCount = sizeof(allStreams) / sizeof(allStreams[0]); 490 camera_metadata_entry_t entry; 491 uint32_t tag = static_cast<uint32_t>(ANDROID_REQUEST_OUTPUT_STREAMS); 492 int find = find_camera_metadata_entry(request, tag, &entry); 493 if (find == -ENOENT) { 494 if (add_camera_metadata_entry(request, tag, &allStreams, 495 /*data_count*/streamCount) != OK) { 496 camera_metadata_t *tmp = allocate_camera_metadata(1000, 10000); 497 ASSERT_OK(append_camera_metadata(tmp, request)); 498 free_camera_metadata(request); 499 request = tmp; 500 501 ASSERT_OK(add_camera_metadata_entry(request, tag, &allStreams, 502 /*data_count*/streamCount)); 503 } 504 } else { 505 ASSERT_OK(update_camera_metadata_entry(request, entry.index, 506 &allStreams, /*data_count*/streamCount, &entry)); 507 } 508 509 EXPECT_OK(mCamera->submitRequest(request, /*streaming*/true)); 510 511 dout << "will sleep now for " << mDisplaySecs << std::endl; 512 sleep(mDisplaySecs); 513 514 free_camera_metadata(request); 515 516 for (int i = 0; i < streamCount; ++i) { 517 EXPECT_OK(mCamera->deleteStream(allStreams[i])); 518 } 519 EXPECT_OK(mCamera->exclusiveUnlock()); 520} 521 522TEST_F(ProCameraTest, CpuConsumerSingle) { 523 if (HasFatalFailure()) { 524 return; 525 } 526 int streamId = -1; 527 EXPECT_OK(mCamera->createStreamCpu(/*width*/320, /*height*/240, 528 TEST_FORMAT_DEPTH, TEST_CPU_HEAP_COUNT, &streamId)); 529 EXPECT_NE(-1, streamId); 530 531 EXPECT_OK(mCamera->exclusiveTryLock()); 532 EXPECT_EQ(OK, mListener->WaitForEvent()); 533 EXPECT_EQ(ACQUIRED, mListener->ReadEvent()); 534 /* iterate in a loop submitting requests every frame. 535 * what kind of requests doesnt really matter, just whatever. 536 */ 537 538 // it would probably be better to use CameraMetadata from camera service. 539 camera_metadata_t *request = NULL; 540 EXPECT_OK(mCamera->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW, 541 /*out*/&request)); 542 EXPECT_NE((void*)NULL, request); 543 544 /*FIXME: dont need this later, at which point the above should become an 545 ASSERT_NE*/ 546 if(request == NULL) request = allocate_camera_metadata(10, 100); 547 548 // set the output streams to just this stream ID 549 550 uint8_t allStreams[] = { streamId }; 551 camera_metadata_entry_t entry; 552 uint32_t tag = static_cast<uint32_t>(ANDROID_REQUEST_OUTPUT_STREAMS); 553 int find = find_camera_metadata_entry(request, tag, &entry); 554 if (find == -ENOENT) { 555 if (add_camera_metadata_entry(request, tag, &allStreams, 556 /*data_count*/1) != OK) { 557 camera_metadata_t *tmp = allocate_camera_metadata(1000, 10000); 558 ASSERT_OK(append_camera_metadata(tmp, request)); 559 free_camera_metadata(request); 560 request = tmp; 561 562 ASSERT_OK(add_camera_metadata_entry(request, tag, &allStreams, 563 /*data_count*/1)); 564 } 565 } else { 566 ASSERT_OK(update_camera_metadata_entry(request, entry.index, 567 &allStreams, /*data_count*/1, &entry)); 568 } 569 570 EXPECT_OK(mCamera->submitRequest(request, /*streaming*/true)); 571 572 // Consume a couple of frames 573 for (int i = 0; i < TEST_CPU_FRAME_COUNT; ++i) { 574 EXPECT_EQ(OK, mListener->WaitForEvent()); 575 EXPECT_EQ(BUFFER_RECEIVED, mListener->ReadEvent()); 576 } 577 578 // Done: clean up 579 free_camera_metadata(request); 580 EXPECT_OK(mCamera->deleteStream(streamId)); 581 EXPECT_OK(mCamera->exclusiveUnlock()); 582} 583 584TEST_F(ProCameraTest, CpuConsumerDual) { 585 if (HasFatalFailure()) { 586 return; 587 } 588 int streamId = -1; 589 EXPECT_OK(mCamera->createStreamCpu(/*width*/1280, /*height*/960, 590 TEST_FORMAT_MAIN, TEST_CPU_HEAP_COUNT, &streamId)); 591 EXPECT_NE(-1, streamId); 592 593 int depthStreamId = -1; 594 EXPECT_OK(mCamera->createStreamCpu(/*width*/320, /*height*/240, 595 TEST_FORMAT_DEPTH, TEST_CPU_HEAP_COUNT, &depthStreamId)); 596 EXPECT_NE(-1, depthStreamId); 597 598 EXPECT_OK(mCamera->exclusiveTryLock()); 599 EXPECT_EQ(OK, mListener->WaitForEvent()); 600 EXPECT_EQ(ACQUIRED, mListener->ReadEvent()); 601 /* 602 */ 603 /* iterate in a loop submitting requests every frame. 604 * what kind of requests doesnt really matter, just whatever. 605 */ 606 607 // it would probably be better to use CameraMetadata from camera service. 608 camera_metadata_t *request = NULL; 609 EXPECT_OK(mCamera->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW, 610 /*out*/&request)); 611 EXPECT_NE((void*)NULL, request); 612 613 if(request == NULL) request = allocate_camera_metadata(10, 100); 614 615 // set the output streams to just this stream ID 616 617 // wow what a verbose API. 618 uint8_t allStreams[] = { streamId, depthStreamId }; 619 size_t streamCount = 2; 620 camera_metadata_entry_t entry; 621 uint32_t tag = static_cast<uint32_t>(ANDROID_REQUEST_OUTPUT_STREAMS); 622 int find = find_camera_metadata_entry(request, tag, &entry); 623 if (find == -ENOENT) { 624 if (add_camera_metadata_entry(request, tag, &allStreams, 625 /*data_count*/streamCount) != OK) { 626 camera_metadata_t *tmp = allocate_camera_metadata(1000, 10000); 627 ASSERT_OK(append_camera_metadata(tmp, request)); 628 free_camera_metadata(request); 629 request = tmp; 630 631 ASSERT_OK(add_camera_metadata_entry(request, tag, &allStreams, 632 /*data_count*/streamCount)); 633 } 634 } else { 635 ASSERT_OK(update_camera_metadata_entry(request, entry.index, 636 &allStreams, /*data_count*/streamCount, &entry)); 637 } 638 639 EXPECT_OK(mCamera->submitRequest(request, /*streaming*/true)); 640 641 // Consume a couple of frames 642 for (int i = 0; i < TEST_CPU_FRAME_COUNT; ++i) { 643 // stream id 1 644 EXPECT_EQ(OK, mListener->WaitForEvent()); 645 EXPECT_EQ(BUFFER_RECEIVED, mListener->ReadEvent()); 646 647 // stream id 2 648 EXPECT_EQ(OK, mListener->WaitForEvent()); 649 EXPECT_EQ(BUFFER_RECEIVED, mListener->ReadEvent()); 650 651 //TODO: events should be a struct with some data like the stream id 652 } 653 654 // Done: clean up 655 free_camera_metadata(request); 656 EXPECT_OK(mCamera->deleteStream(streamId)); 657 EXPECT_OK(mCamera->exclusiveUnlock()); 658} 659 660} 661} 662} 663} 664 665