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