1 /* 2 * Copyright (c) 2012 The WebRTC 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 #include "webrtc/modules/video_render/test/testAPI/testAPI.h" 12 13 #include <stdio.h> 14 15 #if defined(_WIN32) 16 #include <tchar.h> 17 #include <windows.h> 18 #include <assert.h> 19 #include <fstream> 20 #include <iostream> 21 #include <string> 22 #include <windows.h> 23 #include <ddraw.h> 24 25 #elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) 26 27 #include <X11/Xlib.h> 28 #include <X11/Xutil.h> 29 #include <iostream> 30 #include <sys/time.h> 31 32 #endif 33 34 #include "webrtc/common_types.h" 35 #include "webrtc/modules/interface/module_common_types.h" 36 #include "webrtc/modules/utility/interface/process_thread.h" 37 #include "webrtc/modules/video_render/include/video_render.h" 38 #include "webrtc/modules/video_render/include/video_render_defines.h" 39 #include "webrtc/system_wrappers/interface/sleep.h" 40 #include "webrtc/system_wrappers/interface/tick_util.h" 41 #include "webrtc/system_wrappers/interface/trace.h" 42 43 using namespace webrtc; 44 45 void GetTestVideoFrame(I420VideoFrame* frame, 46 uint8_t startColor); 47 int TestSingleStream(VideoRender* renderModule); 48 int TestFullscreenStream(VideoRender* &renderModule, 49 void* window, 50 const VideoRenderType videoRenderType); 51 int TestBitmapText(VideoRender* renderModule); 52 int TestMultipleStreams(VideoRender* renderModule); 53 int TestExternalRender(VideoRender* renderModule); 54 55 #define TEST_FRAME_RATE 30 56 #define TEST_TIME_SECOND 5 57 #define TEST_FRAME_NUM (TEST_FRAME_RATE*TEST_TIME_SECOND) 58 #define TEST_STREAM0_START_COLOR 0 59 #define TEST_STREAM1_START_COLOR 64 60 #define TEST_STREAM2_START_COLOR 128 61 #define TEST_STREAM3_START_COLOR 192 62 63 #if defined(WEBRTC_LINUX) 64 65 #define GET_TIME_IN_MS timeGetTime() 66 67 unsigned long timeGetTime() 68 { 69 struct timeval tv; 70 struct timezone tz; 71 unsigned long val; 72 73 gettimeofday(&tv, &tz); 74 val= tv.tv_sec*1000+ tv.tv_usec/1000; 75 return(val); 76 } 77 78 #elif defined(WEBRTC_MAC) 79 80 #include <unistd.h> 81 82 #define GET_TIME_IN_MS timeGetTime() 83 84 unsigned long timeGetTime() 85 { 86 return 0; 87 } 88 89 #else 90 91 #define GET_TIME_IN_MS ::timeGetTime() 92 93 #endif 94 95 using namespace std; 96 97 #if defined(_WIN32) 98 LRESULT CALLBACK WebRtcWinProc( HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam) 99 { 100 switch(uMsg) 101 { 102 case WM_DESTROY: 103 break; 104 case WM_COMMAND: 105 break; 106 } 107 return DefWindowProc(hWnd,uMsg,wParam,lParam); 108 } 109 110 int WebRtcCreateWindow(HWND &hwndMain,int winNum, int width, int height) 111 { 112 HINSTANCE hinst = GetModuleHandle(0); 113 WNDCLASSEX wcx; 114 wcx.hInstance = hinst; 115 wcx.lpszClassName = TEXT("VideoRenderTest"); 116 wcx.lpfnWndProc = (WNDPROC)WebRtcWinProc; 117 wcx.style = CS_DBLCLKS; 118 wcx.hIcon = LoadIcon (NULL, IDI_APPLICATION); 119 wcx.hIconSm = LoadIcon (NULL, IDI_APPLICATION); 120 wcx.hCursor = LoadCursor (NULL, IDC_ARROW); 121 wcx.lpszMenuName = NULL; 122 wcx.cbSize = sizeof (WNDCLASSEX); 123 wcx.cbClsExtra = 0; 124 wcx.cbWndExtra = 0; 125 wcx.hbrBackground = GetSysColorBrush(COLOR_3DFACE); 126 127 // Register our window class with the operating system. 128 // If there is an error, exit program. 129 if ( !RegisterClassEx (&wcx) ) 130 { 131 MessageBox( 0, TEXT("Failed to register window class!"),TEXT("Error!"), MB_OK|MB_ICONERROR ); 132 return 0; 133 } 134 135 // Create the main window. 136 hwndMain = CreateWindowEx( 137 0, // no extended styles 138 TEXT("VideoRenderTest"), // class name 139 TEXT("VideoRenderTest Window"), // window name 140 WS_OVERLAPPED |WS_THICKFRAME, // overlapped window 141 800, // horizontal position 142 0, // vertical position 143 width, // width 144 height, // height 145 (HWND) NULL, // no parent or owner window 146 (HMENU) NULL, // class menu used 147 hinst, // instance handle 148 NULL); // no window creation data 149 150 if (!hwndMain) 151 return -1; 152 153 // Show the window using the flag specified by the program 154 // that started the application, and send the application 155 // a WM_PAINT message. 156 157 ShowWindow(hwndMain, SW_SHOWDEFAULT); 158 UpdateWindow(hwndMain); 159 return 0; 160 } 161 162 #elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) 163 164 int WebRtcCreateWindow(Window *outWindow, Display **outDisplay, int winNum, int width, int height) // unsigned char* title, int titleLength) 165 166 { 167 int screen, xpos = 10, ypos = 10; 168 XEvent evnt; 169 XSetWindowAttributes xswa; // window attribute struct 170 XVisualInfo vinfo; // screen visual info struct 171 unsigned long mask; // attribute mask 172 173 // get connection handle to xserver 174 Display* _display = XOpenDisplay( NULL ); 175 176 // get screen number 177 screen = DefaultScreen(_display); 178 179 // put desired visual info for the screen in vinfo 180 if( XMatchVisualInfo(_display, screen, 24, TrueColor, &vinfo) != 0 ) 181 { 182 //printf( "Screen visual info match!\n" ); 183 } 184 185 // set window attributes 186 xswa.colormap = XCreateColormap(_display, DefaultRootWindow(_display), vinfo.visual, AllocNone); 187 xswa.event_mask = StructureNotifyMask | ExposureMask; 188 xswa.background_pixel = 0; 189 xswa.border_pixel = 0; 190 191 // value mask for attributes 192 mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; 193 194 switch( winNum ) 195 { 196 case 0: 197 xpos = 200; 198 ypos = 200; 199 break; 200 case 1: 201 xpos = 300; 202 ypos = 200; 203 break; 204 default: 205 break; 206 } 207 208 // create a subwindow for parent (defroot) 209 Window _window = XCreateWindow(_display, DefaultRootWindow(_display), 210 xpos, ypos, 211 width, 212 height, 213 0, vinfo.depth, 214 InputOutput, 215 vinfo.visual, 216 mask, &xswa); 217 218 // Set window name 219 if( winNum == 0 ) 220 { 221 XStoreName(_display, _window, "VE MM Local Window"); 222 XSetIconName(_display, _window, "VE MM Local Window"); 223 } 224 else if( winNum == 1 ) 225 { 226 XStoreName(_display, _window, "VE MM Remote Window"); 227 XSetIconName(_display, _window, "VE MM Remote Window"); 228 } 229 230 // make x report events for mask 231 XSelectInput(_display, _window, StructureNotifyMask); 232 233 // map the window to the display 234 XMapWindow(_display, _window); 235 236 // wait for map event 237 do 238 { 239 XNextEvent(_display, &evnt); 240 } 241 while (evnt.type != MapNotify || evnt.xmap.event != _window); 242 243 *outWindow = _window; 244 *outDisplay = _display; 245 246 return 0; 247 } 248 #endif // LINUX 249 250 // Note: Mac code is in testApi_mac.mm. 251 252 class MyRenderCallback: public VideoRenderCallback 253 { 254 public: 255 MyRenderCallback() : 256 _cnt(0) 257 { 258 } 259 ; 260 ~MyRenderCallback() 261 { 262 } 263 ; 264 virtual int32_t RenderFrame(const uint32_t streamId, 265 I420VideoFrame& videoFrame) 266 { 267 _cnt++; 268 if (_cnt % 100 == 0) 269 { 270 printf("Render callback %d \n",_cnt); 271 } 272 return 0; 273 } 274 int32_t _cnt; 275 }; 276 277 void GetTestVideoFrame(I420VideoFrame* frame, 278 uint8_t startColor) { 279 // changing color 280 static uint8_t color = startColor; 281 282 memset(frame->buffer(kYPlane), color, frame->allocated_size(kYPlane)); 283 memset(frame->buffer(kUPlane), color, frame->allocated_size(kUPlane)); 284 memset(frame->buffer(kVPlane), color, frame->allocated_size(kVPlane)); 285 286 ++color; 287 } 288 289 int TestSingleStream(VideoRender* renderModule) { 290 int error = 0; 291 // Add settings for a stream to render 292 printf("Add stream 0 to entire window\n"); 293 const int streamId0 = 0; 294 VideoRenderCallback* renderCallback0 = renderModule->AddIncomingRenderStream(streamId0, 0, 0.0f, 0.0f, 1.0f, 1.0f); 295 assert(renderCallback0 != NULL); 296 297 printf("Start render\n"); 298 error = renderModule->StartRender(streamId0); 299 if (error != 0) { 300 // TODO(phoglund): This test will not work if compiled in release mode. 301 // This rather silly construct here is to avoid compilation errors when 302 // compiling in release. Release => no asserts => unused 'error' variable. 303 assert(false); 304 } 305 306 // Loop through an I420 file and render each frame 307 const int width = 352; 308 const int half_width = (width + 1) / 2; 309 const int height = 288; 310 311 I420VideoFrame videoFrame0; 312 videoFrame0.CreateEmptyFrame(width, height, width, half_width, half_width); 313 314 const uint32_t renderDelayMs = 500; 315 316 for (int i=0; i<TEST_FRAME_NUM; i++) { 317 GetTestVideoFrame(&videoFrame0, TEST_STREAM0_START_COLOR); 318 // Render this frame with the specified delay 319 videoFrame0.set_render_time_ms(TickTime::MillisecondTimestamp() 320 + renderDelayMs); 321 renderCallback0->RenderFrame(streamId0, videoFrame0); 322 SleepMs(1000/TEST_FRAME_RATE); 323 } 324 325 326 // Shut down 327 printf("Closing...\n"); 328 error = renderModule->StopRender(streamId0); 329 assert(error == 0); 330 331 error = renderModule->DeleteIncomingRenderStream(streamId0); 332 assert(error == 0); 333 334 return 0; 335 } 336 337 int TestFullscreenStream(VideoRender* &renderModule, 338 void* window, 339 const VideoRenderType videoRenderType) { 340 VideoRender::DestroyVideoRender(renderModule); 341 renderModule = VideoRender::CreateVideoRender(12345, window, true, videoRenderType); 342 343 TestSingleStream(renderModule); 344 345 VideoRender::DestroyVideoRender(renderModule); 346 renderModule = VideoRender::CreateVideoRender(12345, window, false, videoRenderType); 347 348 return 0; 349 } 350 351 int TestBitmapText(VideoRender* renderModule) { 352 #if defined(WIN32) 353 354 int error = 0; 355 // Add settings for a stream to render 356 printf("Add stream 0 to entire window\n"); 357 const int streamId0 = 0; 358 VideoRenderCallback* renderCallback0 = renderModule->AddIncomingRenderStream(streamId0, 0, 0.0f, 0.0f, 1.0f, 1.0f); 359 assert(renderCallback0 != NULL); 360 361 printf("Adding Bitmap\n"); 362 DDCOLORKEY ColorKey; // black 363 ColorKey.dwColorSpaceHighValue = RGB(0, 0, 0); 364 ColorKey.dwColorSpaceLowValue = RGB(0, 0, 0); 365 HBITMAP hbm = (HBITMAP)LoadImage(NULL, 366 (LPCTSTR)_T("renderStartImage.bmp"), 367 IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); 368 renderModule->SetBitmap(hbm, 0, &ColorKey, 0.0f, 0.0f, 0.3f, 369 0.3f); 370 371 printf("Adding Text\n"); 372 renderModule->SetText(1, (uint8_t*) "WebRtc Render Demo App", 20, 373 RGB(255, 0, 0), RGB(0, 0, 0), 0.25f, 0.1f, 1.0f, 374 1.0f); 375 376 printf("Start render\n"); 377 error = renderModule->StartRender(streamId0); 378 assert(error == 0); 379 380 // Loop through an I420 file and render each frame 381 const int width = 352; 382 const int half_width = (width + 1) / 2; 383 const int height = 288; 384 385 I420VideoFrame videoFrame0; 386 videoFrame0.CreateEmptyFrame(width, height, width, half_width, half_width); 387 388 const uint32_t renderDelayMs = 500; 389 390 for (int i=0; i<TEST_FRAME_NUM; i++) { 391 GetTestVideoFrame(&videoFrame0, TEST_STREAM0_START_COLOR); 392 // Render this frame with the specified delay 393 videoFrame0.set_render_time_ms(TickTime::MillisecondTimestamp() + 394 renderDelayMs); 395 renderCallback0->RenderFrame(streamId0, videoFrame0); 396 SleepMs(1000/TEST_FRAME_RATE); 397 } 398 // Sleep and let all frames be rendered before closing 399 SleepMs(renderDelayMs*2); 400 401 402 // Shut down 403 printf("Closing...\n"); 404 ColorKey.dwColorSpaceHighValue = RGB(0,0,0); 405 ColorKey.dwColorSpaceLowValue = RGB(0,0,0); 406 renderModule->SetBitmap(NULL, 0, &ColorKey, 0.0f, 0.0f, 0.0f, 0.0f); 407 renderModule->SetText(1, NULL, 20, RGB(255,255,255), 408 RGB(0,0,0), 0.0f, 0.0f, 0.0f, 0.0f); 409 410 error = renderModule->StopRender(streamId0); 411 assert(error == 0); 412 413 error = renderModule->DeleteIncomingRenderStream(streamId0); 414 assert(error == 0); 415 #endif 416 417 return 0; 418 } 419 420 int TestMultipleStreams(VideoRender* renderModule) { 421 int error = 0; 422 423 // Add settings for a stream to render 424 printf("Add stream 0\n"); 425 const int streamId0 = 0; 426 VideoRenderCallback* renderCallback0 = 427 renderModule->AddIncomingRenderStream(streamId0, 0, 0.0f, 0.0f, 0.45f, 0.45f); 428 assert(renderCallback0 != NULL); 429 printf("Add stream 1\n"); 430 const int streamId1 = 1; 431 VideoRenderCallback* renderCallback1 = 432 renderModule->AddIncomingRenderStream(streamId1, 0, 0.55f, 0.0f, 1.0f, 0.45f); 433 assert(renderCallback1 != NULL); 434 printf("Add stream 2\n"); 435 const int streamId2 = 2; 436 VideoRenderCallback* renderCallback2 = 437 renderModule->AddIncomingRenderStream(streamId2, 0, 0.0f, 0.55f, 0.45f, 1.0f); 438 assert(renderCallback2 != NULL); 439 printf("Add stream 3\n"); 440 const int streamId3 = 3; 441 VideoRenderCallback* renderCallback3 = 442 renderModule->AddIncomingRenderStream(streamId3, 0, 0.55f, 0.55f, 1.0f, 1.0f); 443 assert(renderCallback3 != NULL); 444 error = renderModule->StartRender(streamId0); 445 if (error != 0) { 446 // TODO(phoglund): This test will not work if compiled in release mode. 447 // This rather silly construct here is to avoid compilation errors when 448 // compiling in release. Release => no asserts => unused 'error' variable. 449 assert(false); 450 } 451 error = renderModule->StartRender(streamId1); 452 assert(error == 0); 453 error = renderModule->StartRender(streamId2); 454 assert(error == 0); 455 error = renderModule->StartRender(streamId3); 456 assert(error == 0); 457 458 // Loop through an I420 file and render each frame 459 const int width = 352; 460 const int half_width = (width + 1) / 2; 461 const int height = 288; 462 463 I420VideoFrame videoFrame0; 464 videoFrame0.CreateEmptyFrame(width, height, width, half_width, half_width); 465 I420VideoFrame videoFrame1; 466 videoFrame1.CreateEmptyFrame(width, height, width, half_width, half_width); 467 I420VideoFrame videoFrame2; 468 videoFrame2.CreateEmptyFrame(width, height, width, half_width, half_width); 469 I420VideoFrame videoFrame3; 470 videoFrame3.CreateEmptyFrame(width, height, width, half_width, half_width); 471 472 const uint32_t renderDelayMs = 500; 473 474 // Render frames with the specified delay. 475 for (int i=0; i<TEST_FRAME_NUM; i++) { 476 GetTestVideoFrame(&videoFrame0, TEST_STREAM0_START_COLOR); 477 478 videoFrame0.set_render_time_ms(TickTime::MillisecondTimestamp() + 479 renderDelayMs); 480 renderCallback0->RenderFrame(streamId0, videoFrame0); 481 482 GetTestVideoFrame(&videoFrame1, TEST_STREAM1_START_COLOR); 483 videoFrame1.set_render_time_ms(TickTime::MillisecondTimestamp() + 484 renderDelayMs); 485 renderCallback1->RenderFrame(streamId1, videoFrame1); 486 487 GetTestVideoFrame(&videoFrame2, TEST_STREAM2_START_COLOR); 488 videoFrame2.set_render_time_ms(TickTime::MillisecondTimestamp() + 489 renderDelayMs); 490 renderCallback2->RenderFrame(streamId2, videoFrame2); 491 492 GetTestVideoFrame(&videoFrame3, TEST_STREAM3_START_COLOR); 493 videoFrame3.set_render_time_ms(TickTime::MillisecondTimestamp() + 494 renderDelayMs); 495 renderCallback3->RenderFrame(streamId3, videoFrame3); 496 497 SleepMs(1000/TEST_FRAME_RATE); 498 } 499 500 // Shut down 501 printf("Closing...\n"); 502 error = renderModule->StopRender(streamId0); 503 assert(error == 0); 504 error = renderModule->DeleteIncomingRenderStream(streamId0); 505 assert(error == 0); 506 error = renderModule->StopRender(streamId1); 507 assert(error == 0); 508 error = renderModule->DeleteIncomingRenderStream(streamId1); 509 assert(error == 0); 510 error = renderModule->StopRender(streamId2); 511 assert(error == 0); 512 error = renderModule->DeleteIncomingRenderStream(streamId2); 513 assert(error == 0); 514 error = renderModule->StopRender(streamId3); 515 assert(error == 0); 516 error = renderModule->DeleteIncomingRenderStream(streamId3); 517 assert(error == 0); 518 519 return 0; 520 } 521 522 int TestExternalRender(VideoRender* renderModule) { 523 int error = 0; 524 MyRenderCallback *externalRender = new MyRenderCallback(); 525 526 const int streamId0 = 0; 527 VideoRenderCallback* renderCallback0 = 528 renderModule->AddIncomingRenderStream(streamId0, 0, 0.0f, 0.0f, 529 1.0f, 1.0f); 530 assert(renderCallback0 != NULL); 531 error = renderModule->AddExternalRenderCallback(streamId0, externalRender); 532 if (error != 0) { 533 // TODO(phoglund): This test will not work if compiled in release mode. 534 // This rather silly construct here is to avoid compilation errors when 535 // compiling in release. Release => no asserts => unused 'error' variable. 536 assert(false); 537 } 538 539 error = renderModule->StartRender(streamId0); 540 assert(error == 0); 541 542 const int width = 352; 543 const int half_width = (width + 1) / 2; 544 const int height = 288; 545 I420VideoFrame videoFrame0; 546 videoFrame0.CreateEmptyFrame(width, height, width, half_width, half_width); 547 548 const uint32_t renderDelayMs = 500; 549 int frameCount = TEST_FRAME_NUM; 550 for (int i=0; i<frameCount; i++) { 551 videoFrame0.set_render_time_ms(TickTime::MillisecondTimestamp() + 552 renderDelayMs); 553 renderCallback0->RenderFrame(streamId0, videoFrame0); 554 SleepMs(33); 555 } 556 557 // Sleep and let all frames be rendered before closing 558 SleepMs(2*renderDelayMs); 559 560 // Shut down 561 printf("Closing...\n"); 562 error = renderModule->StopRender(streamId0); 563 assert(error == 0); 564 error = renderModule->DeleteIncomingRenderStream(streamId0); 565 assert(error == 0); 566 assert(frameCount == externalRender->_cnt); 567 568 delete externalRender; 569 externalRender = NULL; 570 571 return 0; 572 } 573 574 void RunVideoRenderTests(void* window, VideoRenderType windowType) { 575 int myId = 12345; 576 577 // Create the render module 578 printf("Create render module\n"); 579 VideoRender* renderModule = NULL; 580 renderModule = VideoRender::CreateVideoRender(myId, 581 window, 582 false, 583 windowType); 584 assert(renderModule != NULL); 585 586 // ##### Test single stream rendering #### 587 printf("#### TestSingleStream ####\n"); 588 if (TestSingleStream(renderModule) != 0) { 589 printf ("TestSingleStream failed\n"); 590 } 591 592 // ##### Test fullscreen rendering #### 593 printf("#### TestFullscreenStream ####\n"); 594 if (TestFullscreenStream(renderModule, window, windowType) != 0) { 595 printf ("TestFullscreenStream failed\n"); 596 } 597 598 // ##### Test bitmap and text #### 599 printf("#### TestBitmapText ####\n"); 600 if (TestBitmapText(renderModule) != 0) { 601 printf ("TestBitmapText failed\n"); 602 } 603 604 // ##### Test multiple streams #### 605 printf("#### TestMultipleStreams ####\n"); 606 if (TestMultipleStreams(renderModule) != 0) { 607 printf ("TestMultipleStreams failed\n"); 608 } 609 610 // ##### Test multiple streams #### 611 printf("#### TestExternalRender ####\n"); 612 if (TestExternalRender(renderModule) != 0) { 613 printf ("TestExternalRender failed\n"); 614 } 615 616 delete renderModule; 617 renderModule = NULL; 618 619 printf("VideoRender unit tests passed.\n"); 620 } 621 622 // Note: The Mac main is implemented in testApi_mac.mm. 623 #if defined(_WIN32) 624 int _tmain(int argc, _TCHAR* argv[]) 625 #elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) 626 int main(int argc, char* argv[]) 627 #endif 628 #if !defined(WEBRTC_MAC) && !defined(WEBRTC_ANDROID) 629 { 630 // Create a window for testing. 631 void* window = NULL; 632 #if defined (_WIN32) 633 HWND testHwnd; 634 WebRtcCreateWindow(testHwnd, 0, 352, 288); 635 window = (void*)testHwnd; 636 VideoRenderType windowType = kRenderWindows; 637 #elif defined(WEBRTC_LINUX) 638 Window testWindow; 639 Display* display; 640 WebRtcCreateWindow(&testWindow, &display, 0, 352, 288); 641 VideoRenderType windowType = kRenderX11; 642 window = (void*)testWindow; 643 #endif // WEBRTC_LINUX 644 645 RunVideoRenderTests(window, windowType); 646 return 0; 647 } 648 #endif // !WEBRTC_MAC 649