You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

455 lines
16 KiB

/* compile using: gcc d3d9_player.c -o d3d9_player.exe -I <path/liblc> -llibvlc -ld3d9 */
#include <windows.h>
#include <windowsx.h>
#define COBJMACROS
#include <d3d9.h>
#include <vlc/vlc.h>
#define SCREEN_WIDTH 900
#define SCREEN_HEIGHT 900
#define BORDER_LEFT ( 20)
#define BORDER_RIGHT (700 + BORDER_LEFT)
#define BORDER_TOP ( 10)
#define BORDER_BOTTOM (700 + BORDER_TOP)
struct render_context
{
HWND hWnd;
libvlc_media_player_t *p_mp;
IDirect3D9Ex *d3d;
IDirect3DDevice9 *d3ddev; /* the host app device */
IDirect3DDevice9 *libvlc_d3d; /* the device where VLC can do its rendering */
/* surface we'll use to display on our backbuffer */
IDirect3DTexture9 *renderTexture;
HANDLE sharedHandled;
/* surface that VLC will render to, which is a shared version of renderTexture
* on libvlc_d3d */
IDirect3DTexture9 *sharedRenderTexture;
IDirect3DSurface9 *sharedRenderSurface;
/* our swapchain backbuffer */
IDirect3DSurface9 *backBuffer;
IDirect3DVertexBuffer9 *rectangleFVFVertexBuf;
CRITICAL_SECTION sizeLock; // the ReportSize callback cannot be called during/after the CleanupDevice_cb is called
unsigned width, height;
libvlc_video_output_resize_cb ReportSize;
void *ReportOpaque;
};
struct CUSTOMVERTEX {FLOAT X, Y, Z, RHW; DWORD COLOR;
FLOAT tu, tv; /* texture relative coordinates */};
#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1)
/**
* Callback called when it's time to display the video, in sync with the audio.
*
* This is called outside of the UI thread (in the VLC rendering thread).
*/
static void Swap(struct render_context *ctx)
{
/* finished drawing to our swap surface, now render that surface to the backbuffer */
IDirect3DDevice9_SetRenderTarget(ctx->d3ddev, 0, ctx->backBuffer);
/* clear the backbuffer to orange */
IDirect3DDevice9_Clear(ctx->d3ddev, 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 120, 0), 1.0f, 0);
IDirect3DDevice9_BeginScene(ctx->d3ddev);
IDirect3DDevice9_SetTexture(ctx->d3ddev, 0, (IDirect3DBaseTexture9*)ctx->renderTexture);
IDirect3DDevice9_SetStreamSource(ctx->d3ddev, 0, ctx->rectangleFVFVertexBuf, 0, sizeof(struct CUSTOMVERTEX));
IDirect3DDevice9_SetFVF(ctx->d3ddev, CUSTOMFVF);
IDirect3DDevice9_DrawPrimitive(ctx->d3ddev, D3DPT_TRIANGLEFAN, 0, 2);
IDirect3DDevice9_EndScene(ctx->d3ddev);
IDirect3DDevice9_Present(ctx->d3ddev, NULL, NULL, ctx->hWnd, NULL);
}
/**
* Callback called to tell the size of the surface that should be available
* to VLC to draw into.
*
* This is called outside of the UI thread (not the VLC rendering thread).
*/
static bool Resize(struct render_context *ctx, unsigned width, unsigned height,
IDirect3DDevice9 *vlc_device,
libvlc_video_output_cfg_t *out)
{
HRESULT hr;
D3DDISPLAYMODE d3ddm;
hr = IDirect3D9Ex_GetAdapterDisplayMode(ctx->d3d, 0, &d3ddm);
/* create the output surface VLC will write to */
if (ctx->renderTexture)
{
IDirect3DTexture9_Release(ctx->renderTexture);
ctx->renderTexture = NULL;
ctx->sharedHandled = NULL;
}
if (ctx->sharedRenderTexture)
{
IDirect3DTexture9_Release(ctx->sharedRenderTexture);
ctx->sharedRenderTexture = NULL;
}
if (ctx->sharedRenderSurface)
{
IDirect3DSurface9_Release(ctx->sharedRenderSurface);
ctx->sharedRenderSurface = NULL;
}
/* the device to use may have changed */
if (ctx->libvlc_d3d)
{
IDirect3DDevice9_Release(ctx->libvlc_d3d);
}
ctx->libvlc_d3d = vlc_device;
IDirect3DDevice9_AddRef(ctx->libvlc_d3d);
/* texture we can use on our device */
hr = IDirect3DDevice9_CreateTexture(ctx->d3ddev, width, height, 1, D3DUSAGE_RENDERTARGET,
d3ddm.Format,
D3DPOOL_DEFAULT,
&ctx->renderTexture,
&ctx->sharedHandled);
if (FAILED(hr))
return false;
/* texture/surface that is set as the render target for libvlc on its device */
hr = IDirect3DDevice9_CreateTexture(ctx->libvlc_d3d, width, height, 1, D3DUSAGE_RENDERTARGET,
d3ddm.Format,
D3DPOOL_DEFAULT,
&ctx->sharedRenderTexture,
&ctx->sharedHandled);
if (FAILED(hr))
return false;
hr = IDirect3DTexture9_GetSurfaceLevel(ctx->sharedRenderTexture, 0, &ctx->sharedRenderSurface);
if (FAILED(hr))
return false;
hr = IDirect3DDevice9_SetRenderTarget(ctx->libvlc_d3d, 0, ctx->sharedRenderSurface);
if (FAILED(hr)) return false;
out->d3d9_format = d3ddm.Format;
out->full_range = true;
out->colorspace = libvlc_video_colorspace_BT709;
out->primaries = libvlc_video_primaries_BT709;
out->transfer = libvlc_video_transfer_func_SRGB;
out->orientation = libvlc_video_orient_top_left;
return true;
}
static void init_direct3d(struct render_context *ctx, HWND hWnd)
{
ctx->hWnd = hWnd;
HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &ctx->d3d);
D3DPRESENT_PARAMETERS d3dpp = { 0 };
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
IDirect3D9Ex_CreateDevice(ctx->d3d, D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
NULL,
D3DCREATE_MULTITHREADED| D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE,
&d3dpp,
&ctx->d3ddev);
IDirect3DDevice9_GetRenderTarget(ctx->d3ddev, 0, &ctx->backBuffer);
struct CUSTOMVERTEX rectangleVertices[] =
{
{ BORDER_LEFT, BORDER_TOP, 0.0f, 1.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0f, 0.0f },
{ BORDER_RIGHT, BORDER_TOP, 0.0f, 1.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0f, 0.0f },
{ BORDER_RIGHT, BORDER_BOTTOM, 0.0f, 1.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0f, 1.0f },
{ BORDER_LEFT, BORDER_BOTTOM, 0.0f, 1.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0f, 1.0f },
};
IDirect3DDevice9Ex_CreateVertexBuffer(ctx->d3ddev, sizeof(rectangleVertices),
D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
CUSTOMFVF,
D3DPOOL_DEFAULT,
&ctx->rectangleFVFVertexBuf,
NULL);
LPVOID pVoid;
IDirect3DVertexBuffer9_Lock(ctx->rectangleFVFVertexBuf, 0, 0, (void**)&pVoid, 0);
memcpy(pVoid, rectangleVertices, sizeof(rectangleVertices));
IDirect3DVertexBuffer9_Unlock(ctx->rectangleFVFVertexBuf);
}
static void release_direct3d(struct render_context *ctx)
{
if (ctx->backBuffer)
IDirect3DSurface9_Release(ctx->backBuffer);
if (ctx->renderTexture)
IDirect3DTexture9_Release(ctx->renderTexture);
if (ctx->sharedRenderSurface)
IDirect3DSurface9_Release(ctx->sharedRenderSurface);
if (ctx->sharedRenderTexture)
IDirect3DTexture9_Release(ctx->sharedRenderTexture);
if (ctx->rectangleFVFVertexBuf)
IDirect3DVertexBuffer9_Release(ctx->rectangleFVFVertexBuf);
if (ctx->libvlc_d3d)
IDirect3DDevice9_Release(ctx->libvlc_d3d);
IDirect3DDevice9_Release(ctx->d3ddev);
IDirect3D9_Release(ctx->d3d);
}
static bool SetupDevice_cb( void **opaque, const libvlc_video_setup_device_cfg_t *cfg, libvlc_video_setup_device_info_t *out )
{
struct render_context *ctx = *opaque;
out->d3d9.device = ctx->d3d;
out->d3d9.adapter = D3DADAPTER_DEFAULT;
return true;
}
static void CleanupDevice_cb( void *opaque )
{
/* here we can release all things Direct3D9 for good (if playing only one file) */
struct render_context *ctx = opaque;
if (ctx->libvlc_d3d)
{
IDirect3DDevice9_Release(ctx->libvlc_d3d);
ctx->libvlc_d3d = NULL;
}
}
static void SetReport_cb( void *opaque,
libvlc_video_output_resize_cb report_size_change,
libvlc_video_output_mouse_move_cb report_mouse_move,
libvlc_video_output_mouse_press_cb report_mouse_press,
libvlc_video_output_mouse_release_cb report_mouse_release,
void *report_opaque )
{
struct render_context *ctx = opaque;
EnterCriticalSection(&ctx->sizeLock);
ctx->ReportSize = report_size_change;
ctx->ReportOpaque = report_opaque;
if (ctx->ReportSize != NULL)
{
/* report our initial size */
ctx->ReportSize(ctx->ReportOpaque, ctx->width, ctx->height);
}
LeaveCriticalSection(&ctx->sizeLock);
}
static bool UpdateOutput_cb( void *opaque, const libvlc_video_render_cfg_t *cfg, libvlc_video_output_cfg_t *out )
{
struct render_context *ctx = opaque;
return Resize(ctx, cfg->width, cfg->height, (IDirect3DDevice9*)cfg->device, out);
}
static void Swap_cb( void* opaque )
{
struct render_context *ctx = opaque;
Swap( ctx );
}
/**
* Callback called just before VLC starts/finishes drawing the video.
*
* Set the surface VLC will render to (could be the backbuffer if nothing else
* needs to be displayed). And then call BeginScene().
*
* This is called outside of the UI thread (in the VLC rendering thread).
*/
static bool StartRendering_cb( void *opaque, bool enter )
{
struct render_context *ctx = opaque;
if ( enter )
{
/* we already set the RenderTarget on the IDirect3DDevice9 */
return true;
}
/* VLC has finished preparing drawning on our surface, we need do the drawing now
so the surface is finished rendering when Swap() is called to do our own
rendering */
IDirect3DDevice9_Present(ctx->libvlc_d3d, NULL, NULL, NULL, NULL);
return true;
}
static const char *AspectRatio = NULL;
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if( message == WM_CREATE )
{
/* Store p_mp for future use */
CREATESTRUCT *c = (CREATESTRUCT *)lParam;
SetWindowLongPtr( hWnd, GWLP_USERDATA, (LONG_PTR)c->lpCreateParams );
return 0;
}
LONG_PTR p_user_data = GetWindowLongPtr( hWnd, GWLP_USERDATA );
if( p_user_data == 0 )
return DefWindowProc(hWnd, message, wParam, lParam);
struct render_context *ctx = (struct render_context *)p_user_data;
switch(message)
{
case WM_SIZE:
{
/* tell libvlc that our size has changed */
ctx->width = (BORDER_RIGHT - BORDER_LEFT) * LOWORD(lParam) / SCREEN_WIDTH; /* remove the orange part ! */
ctx->height = (BORDER_BOTTOM - BORDER_TOP) * HIWORD(lParam) / SCREEN_HEIGHT;
EnterCriticalSection(&ctx->sizeLock);
if (ctx->ReportSize != NULL)
ctx->ReportSize(ctx->ReportOpaque, ctx->width, ctx->height);
LeaveCriticalSection(&ctx->sizeLock);
}
break;
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
break;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
int key = tolower( MapVirtualKey( (UINT)wParam, 2 ) );
if (key == 'a')
{
if (AspectRatio == NULL)
AspectRatio = "16:10";
else if (strcmp(AspectRatio,"16:10")==0)
AspectRatio = "16:9";
else if (strcmp(AspectRatio,"16:9")==0)
AspectRatio = "4:3";
else if (strcmp(AspectRatio,"4:3")==0)
AspectRatio = "185:100";
else if (strcmp(AspectRatio,"185:100")==0)
AspectRatio = "221:100";
else if (strcmp(AspectRatio,"221:100")==0)
AspectRatio = "235:100";
else if (strcmp(AspectRatio,"235:100")==0)
AspectRatio = "239:100";
else if (strcmp(AspectRatio,"239:100")==0)
AspectRatio = "5:3";
else if (strcmp(AspectRatio,"5:3")==0)
AspectRatio = "5:4";
else if (strcmp(AspectRatio,"5:4")==0)
AspectRatio = "1:1";
else if (strcmp(AspectRatio,"1:1")==0)
AspectRatio = "fill";
else if (strcmp(AspectRatio,"fill")==0)
AspectRatio = NULL;
libvlc_video_set_aspect_ratio( ctx->p_mp, AspectRatio );
}
break;
}
default: break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
struct render_context Context = { 0 };
char *file_path;
libvlc_instance_t *p_libvlc;
libvlc_media_t *p_media;
(void)hPrevInstance;
/* remove "" around the given path */
if (lpCmdLine[0] == '"')
{
file_path = _strdup( lpCmdLine+1 );
if (file_path[strlen(file_path)-1] == '"')
file_path[strlen(file_path)-1] = '\0';
}
else
file_path = _strdup( lpCmdLine );
p_libvlc = libvlc_new( 0, NULL );
p_media = libvlc_media_new_path( file_path );
free( file_path );
Context.p_mp = libvlc_media_player_new_from_media( p_libvlc, p_media );
InitializeCriticalSection(&Context.sizeLock);
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = TEXT("WindowClass");
RegisterClassEx(&wc);
RECT wr = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
hWnd = CreateWindowEx(0,
TEXT("WindowClass"),
TEXT("libvlc Demo app"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
wr.right - wr.left,
wr.bottom - wr.top,
NULL,
NULL,
hInstance,
&Context);
ShowWindow(hWnd, nCmdShow);
init_direct3d(&Context, hWnd);
// DON'T use with callbacks libvlc_media_player_set_hwnd(p_mp, hWnd);
/* Tell VLC to render into our D3D9 environment */
libvlc_video_set_output_callbacks( Context.p_mp, libvlc_video_engine_d3d9,
SetupDevice_cb, CleanupDevice_cb, SetReport_cb,
UpdateOutput_cb, Swap_cb, StartRendering_cb,
NULL, NULL, NULL,
&Context );
libvlc_media_player_play( Context.p_mp );
MSG msg;
while(TRUE)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(msg.message == WM_QUIT)
break;
}
libvlc_media_player_stop_async( Context.p_mp );
libvlc_media_player_release( Context.p_mp );
libvlc_media_release( p_media );
libvlc_release( p_libvlc );
DeleteCriticalSection(&Context.sizeLock);
release_direct3d(&Context);
return (int)msg.wParam;
}