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
43using namespace webrtc;
44
45void GetTestVideoFrame(I420VideoFrame* frame,
46                       uint8_t startColor);
47int TestSingleStream(VideoRender* renderModule);
48int TestFullscreenStream(VideoRender* &renderModule,
49                         void* window,
50                         const VideoRenderType videoRenderType);
51int TestBitmapText(VideoRender* renderModule);
52int TestMultipleStreams(VideoRender* renderModule);
53int 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
67unsigned 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
84unsigned long timeGetTime()
85{
86    return 0;
87}
88
89#else
90
91#define GET_TIME_IN_MS ::timeGetTime()
92
93#endif
94
95using namespace std;
96
97#if defined(_WIN32)
98LRESULT 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
110int 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
164int 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
252class MyRenderCallback: public VideoRenderCallback
253{
254public:
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
277void 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
289int 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
337int 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
351int 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
420int 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
522int 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
574void 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)
624int _tmain(int argc, _TCHAR* argv[])
625#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
626int 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