#include "precompile.h" #include "videorender.h" #define av_always_inline __inline #define inline __inline #include #include #include "video_common/ffmpeg_api_adapter.h" //#pragma comment(lib, "ffmpeg\\libswscale.lib") //#pragma comment(lib, "Winmm.lib") //#pragma comment(lib, "Vfw32.lib") #define DEFAULT_MAX_QUEUE_SIZE 16 #define TIMER_RESOLUTION 1 #define CMB_VIDEOVIEW _T("cmb_videoview") struct render_window { volatile HWND hWnd; HANDLE ui_thread; struct videorender *render; }; struct videorender { volatile LONG quit; HANDLE thread_run; struct SwsContext *sws_context; CRITICAL_SECTION q_cs; HANDLE q_full_sem; HANDLE q_empty_sem; HANDLE q_exit_evt; video_frame** frame; video_timestamp* time; int q_head; int q_tail; int q_size; struct render_window *own_hwnd; /* if hWnd is null, we will create our own window */ HWND hWnd; int top; int left; int width; int height; unsigned char *rgb; void (*free_frame_cb)(video_frame *frame); HDRAWDIB hdib; }; static void display_frame(struct videorender *pr, video_frame *frame) { int dstFormat; int srcFormat; int dstStride[4] = {0}; if (frame->format == VIDEO_FORMAT_RGB24) { srcFormat = PIX_FMT_RGB24; } else if (frame->format == VIDEO_FORMAT_I420) { srcFormat = PIX_FMT_YUV420P; } else { return; } dstFormat = PIX_FMT_RGB24; dstStride[0] = pr->width * 3; /* prepare sws_context */ pr->sws_context = sws_getCachedContext(pr->sws_context, frame->width, frame->height, srcFormat, pr->width, pr->height, dstFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL); if (!pr->sws_context) return; /* color space convert and image scale */ { unsigned char *data[4] = {pr->rgb, 0, 0, 0}; sws_scale(pr->sws_context, frame->data, frame->linesize, 0, frame->height, data, dstStride); } /* draw */ { HDC hdc; BITMAPINFOHEADER bmpheader = {sizeof(BITMAPINFOHEADER)}; bmpheader.biPlanes = 1; bmpheader.biBitCount = 24;//pr->bpp; bmpheader.biCompression = BI_RGB; bmpheader.biHeight = pr->height; bmpheader.biWidth = pr->width; hdc = GetDC(pr->hWnd); DrawDibDraw(pr->hdib, hdc, 0, 0, -1, -1, &bmpheader, pr->rgb, 0, 0, pr->width, pr->height, DDF_SAME_DRAW|DDF_SAME_HDC); ReleaseDC(pr->hWnd, hdc); } } static LRESULT CALLBACK UIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_CLOSE: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; } static unsigned __stdcall ui_proc(void *param) { struct render_window *win = (struct render_window *)param; MSG msg; WNDCLASS wc; int width, height; //CoInitialize(NULL); /* register class */ wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hInstance = NULL; wc.style = CS_HREDRAW|CS_VREDRAW|CS_NOCLOSE; wc.lpszMenuName = NULL; wc.lpszClassName = CMB_VIDEOVIEW; wc.lpfnWndProc = &UIWndProc; RegisterClass(&wc); height = win->render->height + 2*GetSystemMetrics(SM_CYBORDER) + 2*GetSystemMetrics(SM_CYEDGE) + GetSystemMetrics(SM_CYCAPTION); width = win->render->width + 2*GetSystemMetrics(SM_CXBORDER) + 2*GetSystemMetrics(SM_CXEDGE); win->hWnd = CreateWindowEx(WS_EX_TOPMOST, CMB_VIDEOVIEW, _T("cmb video view"), WS_CAPTION|WS_POPUPWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, NULL, NULL); //SetWindowPos(win->hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } //CoUninitialize(); _endthreadex(0); return 0; } unsigned __stdcall run(void *param) { struct videorender *pr; video_timestamp ts_freq; HANDLE wait_evt; HANDLE hwait[3]; if (video_get_timestamp_freq(&ts_freq) != 0) { _endthreadex(0); return 0; } if (!(wait_evt = CreateEvent(NULL, FALSE, FALSE, NULL))) { _endthreadex(0); return 0; } /* set time resolution */ { TIMECAPS tc; timeGetDevCaps(&tc, sizeof(TIMECAPS)); timeBeginPeriod(min(max(TIMER_RESOLUTION ,tc.wPeriodMin) ,tc.wPeriodMax)); } /* set affinity mask */ { SYSTEM_INFO si; GetSystemInfo(&si); if (si.dwNumberOfProcessors > 1) SetThreadAffinityMask(GetCurrentThread(), 0x01 << rand()%si.dwNumberOfProcessors); } pr = (struct videorender*)param; hwait[0] = pr->q_full_sem; hwait[1] = pr->q_exit_evt; hwait[2] = wait_evt; while (!pr->quit) { DWORD dwResult = WaitForMultipleObjects(2, &hwait[0], FALSE, INFINITE) - WAIT_OBJECT_0; if (dwResult == 0) { video_timestamp now; video_frame *disp_frame; video_timestamp disp_ts; EnterCriticalSection(&pr->q_cs); disp_frame = pr->frame[pr->q_head]; disp_ts = pr->time[pr->q_head]; pr->q_head = (pr->q_head+1)%pr->q_size; LeaveCriticalSection(&pr->q_cs); video_get_timestamp(&now); if (disp_ts.u64 > now.u64) { /* too early */ UINT delay = (UINT)(1000*(disp_ts.u64 - now.u64)/ts_freq.u64); timeSetEvent(delay, TIMER_RESOLUTION, (LPTIMECALLBACK)wait_evt, 0, TIME_CALLBACK_EVENT_SET|TIME_ONESHOT); dwResult = WaitForMultipleObjects(2, &hwait[1], FALSE, INFINITE) - WAIT_OBJECT_0; if (dwResult == 1) /* wait finished */ display_frame(pr, disp_frame); } else { display_frame(pr, disp_frame); } if (pr->free_frame_cb) { pr->free_frame_cb(disp_frame); } ReleaseSemaphore(pr->q_empty_sem, 1, NULL); } } /* exit, need to clear all queue frames */ if (pr->free_frame_cb) { while (WaitForSingleObject(pr->q_full_sem, 0) == WAIT_OBJECT_0) { pr->free_frame_cb(pr->frame[pr->q_head]); pr->q_head = (pr->q_head + 1)%pr->q_size; } } /* end timer period */ { TIMECAPS tc; timeGetDevCaps(&tc, sizeof(TIMECAPS)); timeEndPeriod(min(max(TIMER_RESOLUTION ,tc.wPeriodMin) ,tc.wPeriodMax)); } CloseHandle(wait_evt); _endthreadex(0); return 0; } int videorender_create(HWND hWnd, int top, int left, int width, int height, int q_max_size, void (*free_frame_cb)(video_frame *frame), videorender_t *p_render) { struct videorender *pr; if (width%2 != 0) return -1; if (height%2 != 0) return -1; if (hWnd && !IsWindow(hWnd)) return -1; pr = (struct videorender *)malloc(sizeof(struct videorender)); if (!pr) return -1; memset(pr, 0, sizeof(struct videorender)); pr->top = top; pr->width = width; pr->height = height; pr->q_size = q_max_size; if (!hWnd) { struct render_window *win = malloc(sizeof(struct render_window)); if (!win) goto on_error; win->hWnd = NULL; win->render = pr; win->ui_thread = (HANDLE)_beginthreadex(NULL, 0, &ui_proc, win, 0, 0); if (!win->ui_thread) { free(win); goto on_error; } while (win->hWnd == NULL) Sleep(1); pr->own_hwnd = win; hWnd = win->hWnd; } pr->hWnd = hWnd; pr->frame = malloc(sizeof(video_frame*)*q_max_size); if (!pr->frame) goto on_error; pr->time = malloc(sizeof(video_timestamp)*q_max_size); if (!pr->time) goto on_error; pr->q_full_sem = CreateSemaphore(NULL, 0, q_max_size, NULL); if (!pr->q_full_sem) goto on_error; pr->q_empty_sem = CreateSemaphore(NULL, q_max_size, q_max_size, NULL); if (!pr->q_empty_sem) goto on_error; pr->q_exit_evt = CreateEvent(NULL, FALSE, FALSE, NULL); if (!pr->q_exit_evt) goto on_error; pr->rgb = malloc(3 * pr->width * pr->height); if (!pr->rgb) goto on_error; /* setup drawdib */ { HDC hdc; BITMAPINFOHEADER bmpheader = {sizeof(BITMAPINFOHEADER)}; bmpheader.biPlanes = 1; bmpheader.biBitCount = 24; bmpheader.biCompression = BI_RGB; bmpheader.biHeight = pr->height; bmpheader.biWidth = pr->width; pr->hdib = DrawDibOpen(); if (!pr->hdib) goto on_error; hdc = GetDC(hWnd); if (!DrawDibBegin(pr->hdib, hdc, pr->width, pr->height, &bmpheader, pr->width, pr->height, DDF_SAME_DRAW|DDF_SAME_HDC)) { DrawDibClose(pr->hdib); pr->hdib = NULL; ReleaseDC(hWnd, hdc); goto on_error; } ReleaseDC(hWnd, hdc); } pr->free_frame_cb = free_frame_cb; /* start working thread */ pr->thread_run = (HANDLE)_beginthreadex(NULL, 0, &run, pr, 0, 0); if (!pr->thread_run) goto on_error; InitializeCriticalSection(&pr->q_cs); *p_render = pr; return 0; on_error: if (pr->frame) free(pr->frame); if (pr->time) free(pr->time); if (pr->q_full_sem) CloseHandle(pr->q_full_sem); if (pr->q_empty_sem) CloseHandle(pr->q_empty_sem); if (pr->q_exit_evt) CloseHandle(pr->q_exit_evt); if (pr->rgb) free(pr->rgb); if (pr->hdib) { DrawDibEnd(pr->hdib); DrawDibClose(pr->hdib); } if (pr->own_hwnd) { PostMessage(pr->own_hwnd->hWnd, WM_CLOSE, 0, 0); WaitForSingleObject(pr->own_hwnd->ui_thread, INFINITE); CloseHandle(pr->own_hwnd->ui_thread); free(pr->own_hwnd); } if (pr->thread_run) CloseHandle(pr->thread_run); free(pr); return -1; } void videorender_destroy(videorender_t render) { if (!render) return; /* change quit flag to true and force mem fence */ InterlockedExchange(&render->quit, 1); /* signal exit event */ SetEvent(render->q_exit_evt); WaitForSingleObject(render->thread_run, INFINITE); CloseHandle(render->q_exit_evt); CloseHandle(render->thread_run); CloseHandle(render->q_full_sem); CloseHandle(render->q_empty_sem); DeleteCriticalSection(&render->q_cs); free(render->frame); free(render->time); if (render->sws_context) sws_freeContext(render->sws_context); free(render->rgb); DrawDibEnd(render->hdib); DrawDibClose(render->hdib); if (render->own_hwnd) { PostMessage(render->own_hwnd->hWnd, WM_CLOSE, 0, 0); WaitForSingleObject(render->own_hwnd->ui_thread, INFINITE); CloseHandle(render->own_hwnd->ui_thread); free(render->own_hwnd); } free(render); return; } int videorender_queue_frame(videorender_t render, video_frame *frame, video_timestamp *disp_ts) { DWORD dwResult; if (!frame || !disp_ts) return 0; if (render->quit) return -1; dwResult = WaitForSingleObject(render->q_empty_sem, 0); if (dwResult == WAIT_OBJECT_0) { EnterCriticalSection(&render->q_cs); render->frame[render->q_tail] = frame; render->time[render->q_tail].u64 = disp_ts->u64; render->q_tail = (render->q_tail+1)%render->q_size; LeaveCriticalSection(&render->q_cs); ReleaseSemaphore(render->q_full_sem, 1, NULL); return 0; } else { return -1; /* no empty slot */ } }