winamp/Src/Plugins/Input/in_dshow/dshowrender.cpp
2024-09-24 14:54:57 +02:00

583 lines
16 KiB
C++

//
// This file contains code that supports an alternate method playing dshow data...
// This will have dshow take control of rendering the data (audio or video).
// Used for mms streams
//
#ifdef WINAMPX
#include <windows.h>
#include <math.h>
#include "message.h"
#include "../jnetlib/jnetlib.h"
#include "in2.h"
#include <AtlBase.h>
#include <streams.h>
#include <qedit.h>
#include <qnetwork.h>
#ifdef DEFGUID
#include <initguid.h> // declares DEFINE_GUID to declare an EXTERN_C const.
#endif
#define IPC_GET_IVIDEOOUTPUT 500
// Externs
extern HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister);
extern void RemoveFromRot(DWORD pdwRegister);
extern bool ReportMissingCodec(char *fn);
extern In_Module mod; // the output module (filled in near the bottom of this file)
extern char lastfn[MAX_PATH]; // currently playing file (used for getting info on the current file)
extern char lastfn_status[256];
extern int file_length; // file length, in bytes
extern int decode_pos_ms; // current decoding position, in milliseconds.
extern int paused; // are we paused?
extern volatile int seek_needed; // if != -1, it is the point that the decode thread should seek to, in ms.
extern int m_length;
extern int paused; // are we paused?
extern bool s_using_dsr;
// Static Vars and Defines
class IVideoOutput
{
public:
virtual ~IVideoOutput() { }
virtual int open(int w, int h, int vflip, double aspectratio, unsigned int fmt)=0;
virtual void setcallback(LRESULT (*msgcallback)(void *token, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam), void *token) { }
virtual void close()=0;
virtual void draw(void *frame)=0;
virtual void drawSubtitle(/*SubsItem **/ void *item) { }
virtual void showStatusMsg(const char *text) { }
virtual int get_latency() { return 0; }
virtual void notifyBufferState(int bufferstate) { } /* 0-255*/
virtual int extended(int param1, int param2, int param3) { return 0; } // Dispatchable, eat this!
};
static IVideoOutput * m_video_output;
static CComPtr<IGraphBuilder> s_pGraphBuilder;
static CComPtr<IMediaEventEx> s_pMediaEventEx;
static CComPtr<IMediaControl> s_pMediaControl;
static CComPtr<IVideoWindow> s_pVideoWindow;
static CComPtr<IBasicAudio> s_pBasicAudio;
static CComPtr<IBasicVideo> s_pBasicVideo;
static CComPtr<IMediaSeeking> s_pMediaSeeking;
static HWND s_dsr_notif_hwnd = NULL;
static HWND s_hVideoWnd = NULL;
static WNDPROC s_OriginalVideoWndProc = NULL;
static RECT s_parentRect;
static DWORD GraphEdit_dwRegister = 0;
static int s_buffering = 0;
static int s_bufferstat = 0;
static BOOL s_bAudioOnly;
static int s_setVolumeOnStart = -1;
// Forward Declarations
LRESULT CALLBACK dsr_TimerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK dsr_SubclassParentWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void dsr_setvolume(int volume);
void dsr_handleNotifyEvents();
void dsr_stop();
// Macros
#define BeginEnumFilters(pFilterGraph, pEnumFilters, pBaseFilter) \
{CComPtr<IEnumFilters> pEnumFilters; \
if(pFilterGraph && SUCCEEDED(pFilterGraph->EnumFilters(&pEnumFilters))) \
{ \
for(CComPtr<IBaseFilter> pBaseFilter; S_OK == pEnumFilters->Next(1, &pBaseFilter, 0); pBaseFilter = NULL) \
{ \
#define EndEnumFilters }}}
static void SendStatus(int status, int arg ) {
mod.fire_winampstatus( status, arg );
}
//------------------------------------------------------------------------------
// Name: CheckVisibility()
// Desc: Set global values for presence of video window.
//------------------------------------------------------------------------------
bool CheckVisibility(void) // returns false for failure
{
HRESULT hr;
if ((!s_pVideoWindow) || (!s_pBasicVideo))
{
s_bAudioOnly = TRUE; // Audio-only files have no video interfaces.
return TRUE;
}
s_bAudioOnly = FALSE;
long lVisible;
hr = s_pVideoWindow->get_Visible(&lVisible); // If this is an audio-only clip, get_Visible() won't work.
if ((FAILED(hr)) && (hr == E_NOINTERFACE))
{
s_bAudioOnly = TRUE;
return TRUE;
}
return !FAILED(hr);
}
static RECT fitMediaToWindow(RECT rectWindow, int mediaWidth, int mediaHeight)
{
RECT retval;
int windowWidth = rectWindow.right - rectWindow.left;
int windowHeight = rectWindow.bottom - rectWindow.top;
if (mediaHeight*windowWidth > mediaWidth*windowHeight)
{
// Gap is on left&right sides
int nOutWidth = windowHeight* mediaWidth / mediaHeight;
int nGap = (windowWidth - nOutWidth)/2;
retval.top = rectWindow.top;
retval.bottom = retval.top + windowHeight;
retval.left = rectWindow.left + nGap;
retval.right = retval.left + nOutWidth;
}
else
{
// Gap is on the top/bottom sides
int nOutHeight = windowWidth* mediaHeight / mediaWidth;
int nGap = (windowHeight - nOutHeight)/2;
retval.left = rectWindow.left;
retval.right = retval.left + windowWidth;
retval.top = rectWindow.top + nGap;
retval.bottom = retval.top + nOutHeight;
}
return retval;
}
void dsr_releaseObjects()
{
if(s_dsr_notif_hwnd)
{
KillTimer(s_dsr_notif_hwnd,112);
DestroyWindow(s_dsr_notif_hwnd);
}
s_dsr_notif_hwnd=NULL;
if ((s_OriginalVideoWndProc) && (s_hVideoWnd))
{
SetWindowLong(s_hVideoWnd, GWL_WNDPROC, (LONG_PTR)s_OriginalVideoWndProc);
s_OriginalVideoWndProc = NULL;
s_hVideoWnd = NULL;
}
s_pMediaEventEx = NULL;
s_pGraphBuilder = NULL;
s_pMediaControl = NULL;
s_pVideoWindow = NULL;
s_pBasicAudio = NULL;
s_pBasicVideo = NULL;
s_pMediaSeeking = NULL;
s_using_dsr = false;
}
// Prefix used for functions here are dsd_ (
int dsr_play(char *fn)
{
HRESULT hr;
hr = s_pGraphBuilder.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER);
if (FAILED(hr)) return 1;
#ifdef _DEBUG
AddToRot(s_pGraphBuilder, &GraphEdit_dwRegister);
#endif
s_pGraphBuilder->QueryInterface(IID_IMediaEvent, (void **)&s_pMediaEventEx);
if(!s_pMediaEventEx)
{
dsr_releaseObjects();
return 1;
}
m_video_output=(IVideoOutput *)SendMessage(mod.hMainWindow,WM_USER,0,IPC_GET_IVIDEOOUTPUT);
if(!m_video_output)
{
dsr_releaseObjects();
return -200; // Can't play file
}
// Create window that will receive filter notifications
static int classReg=0;
if(!classReg)
{
WNDCLASS wc={0,};
wc.style = CS_DBLCLKS;
wc.lpfnWndProc = dsr_TimerWndProc;
wc.hInstance = mod.hDllInstance;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.lpszClassName = "in_dshowClass2";
if (!RegisterClass(&wc))
{
dsr_releaseObjects();
return 1;
}
classReg=1;
}
s_dsr_notif_hwnd=CreateWindow("in_dshowClass2","dshow_notif2",NULL,0,0,1,1,NULL,NULL,mod.hDllInstance,NULL);
SetTimer(s_dsr_notif_hwnd,112,500,0);
// Build a normal graph (start rendering)
WCHAR f[4096];
MultiByteToWideChar(CP_ACP,0,fn,lstrlen(fn)+1,f,4096);
hr = s_pGraphBuilder->RenderFile(f,NULL);
if (FAILED(hr)) {
dsr_handleNotifyEvents();
dsr_releaseObjects();
if ((hr == CLASS_E_CLASSNOTAVAILABLE) || (hr == VFW_E_UNSUPPORTED_VIDEO) || (hr == VFW_E_NO_DECOMPRESSOR))
{
if (ReportMissingCodec(fn)) // returns true if we sent a message
return -500; // Unsupported format
return -200; // Can't play file
}
return 1;
}
// Check if it's a partial playing of the file (likely video codec missing)
if ((hr == VFW_S_PARTIAL_RENDER) || (hr == VFW_S_VIDEO_NOT_RENDERED))
{
if (!ReportMissingCodec(fn)) // Report the missing codec if we can determine it
mod.fire_winampstatus(WINAMPX_STATUS_MISSING_AVI_CODEC, 0); // If we can't report a null codec missing
}
if (FAILED(s_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&s_pMediaControl)) ||
FAILED(s_pGraphBuilder->QueryInterface(IID_IMediaSeeking, (void **)&s_pMediaSeeking)) ||
FAILED(s_pMediaSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME)))
{
dsr_releaseObjects();
return 1;
}
// Get length of file
int mylength = -1;
LONGLONG length;
if (SUCCEEDED(s_pMediaSeeking->GetDuration(&length)))
{
mylength=(int)(length/10000);
}
m_length = mylength;
// Query for video interfaces, which may not be relevant for audio files
s_pGraphBuilder->QueryInterface(IID_IVideoWindow, (void **)&s_pVideoWindow);
s_pGraphBuilder->QueryInterface(IID_IBasicVideo, (void **)&s_pBasicVideo);
// Query for audio interfaces, which may not be relevant for video-only files
s_pGraphBuilder->QueryInterface(IID_IBasicAudio, (void **)&s_pBasicAudio);
if (s_setVolumeOnStart != -1)
{
dsr_setvolume(s_setVolumeOnStart);
s_setVolumeOnStart = -1;
}
// Is this an audio-only file (no video component)?
CheckVisibility();
if (!s_bAudioOnly)
{
m_video_output->open(1, 1, 0, 1.0f, VIDEO_MAKETYPE('N','O','N','E')); // Dummy Size of 1x1
s_hVideoWnd = (HWND)m_video_output->extended(VIDUSER_GET_VIDEOHWND, 0, 0);
InvalidateRect(s_hVideoWnd, NULL, TRUE);
// Setup the video window
s_pVideoWindow->put_Owner((OAHWND) s_hVideoWnd);
s_pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
RECT grc;
GetClientRect(s_hVideoWnd, &grc);
s_parentRect = grc;
if ((s_pBasicVideo) && (s_pVideoWindow))
{
long videoWidth, videoHeight;
if (SUCCEEDED(s_pBasicVideo->GetVideoSize(&videoWidth, &videoHeight)))
{
RECT r = fitMediaToWindow(grc, videoWidth, videoHeight);
s_pVideoWindow->SetWindowPosition(r.left, r.top, r.right-r.left, r.bottom-r.top);
}
}
// Intercept resize messages
s_OriginalVideoWndProc = (WNDPROC) SetWindowLong(s_hVideoWnd, GWL_WNDPROC, (LONG_PTR)dsr_SubclassParentWndProc);
}
// Run the graph to play the media file
hr = s_pMediaControl->Run();
if (FAILED(hr))
{
dsr_stop();
return 1;
}
return 0;
}
// stop playing.
void dsr_stop()
{
if (s_pMediaControl)
s_pMediaControl->Stop();
if (s_pVideoWindow)
{
s_pVideoWindow->put_Visible(OAFALSE);
s_pVideoWindow->put_Owner(NULL);
}
if (m_video_output)
m_video_output->close();
mod.outMod->Close();
mod.SAVSADeInit();
dsr_releaseObjects();
m_length=-1;
lastfn[0]=0;
}
// Standard Pause Routines
void dsr_pause() {
paused=1;
if (s_pMediaControl)
{
s_pMediaControl->Pause();
}
}
void dsr_unpause() {
paused=0;
if (s_pMediaControl)
{
s_pMediaControl->Run();
}
}
int dsr_getoutputtime() {
if (s_bufferstat)
return s_bufferstat;
if (s_pMediaSeeking)
{
LONGLONG pos;
if (SUCCEEDED(s_pMediaSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME)) &&
SUCCEEDED(s_pMediaSeeking->GetCurrentPosition(&pos)))
{
return (int)(pos/10000);
}
}
return 0;
}
void dsr_setoutputtime(int time_in_ms) {
if(s_pMediaSeeking) {
DWORD dwCaps = AM_SEEKING_CanSeekAbsolute;
if (s_pMediaSeeking->CheckCapabilities(&dwCaps) == S_OK)
{
int oldpause=paused;
if(oldpause) dsr_unpause();
LONGLONG l=((LONGLONG)time_in_ms)*10000;
s_pMediaSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);
s_pMediaSeeking->SetPositions(&l,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);
mod.outMod->Flush(time_in_ms);
if(oldpause) dsr_pause();
}
}
}
void dsr_setvolume(int volume)
{
if (s_pBasicAudio)
{
volume = min(volume, 255);
volume = max(volume, 0);
s_pBasicAudio->put_Volume(log(volume+1)/log(256)*10000-10000); // Map (0,255) to (-10000,0) log scale (as ActiveX is in DB's)
}
else
{
s_setVolumeOnStart = volume;
}
}
LRESULT CALLBACK dsr_TimerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(uMsg==WM_TIMER && wParam==112 && s_dsr_notif_hwnd)
{
dsr_handleNotifyEvents();
if(s_buffering)
{
BeginEnumFilters(s_pGraphBuilder, pEF, pBF)
{
if(CComQIPtr<IAMNetworkStatus, &IID_IAMNetworkStatus> pAMNS = pBF)
{
long BufferingProgress = 0;
if(SUCCEEDED(pAMNS->get_BufferingProgress(&BufferingProgress)) && BufferingProgress > 0)
{
wsprintf(lastfn_status,"Buffering (%ld%%)",BufferingProgress);
if(m_video_output) m_video_output->extended(VIDUSER_SET_INFOSTRING,(int)&lastfn_status,0);
int bpos=BufferingProgress;
int csa = mod.SAGetMode();
char tempdata[75*2]={0,};
int x;
if (csa&1)
{
for (x = 0; x < bpos*75/100; x ++)
{
tempdata[x]=x*16/75;
}
}
if (csa&2)
{
int offs=(csa&1) ? 75 : 0;
x=0;
while (x < bpos*75/100)
{
tempdata[offs + x++]=-6+x*14/75;
}
while (x < 75)
{
tempdata[offs + x++]=0;
}
}
if (csa==4)
{
tempdata[0]=tempdata[1]=(bpos*127/100);
}
if (csa) mod.SAAdd(tempdata,++s_bufferstat,(csa==3)?0x80000003:csa);
PostMessage(mod.hMainWindow,WM_USER,0,243);
SendStatus(WINAMPX_STATUS_PREBUFFERING_PCT, 255*BufferingProgress/100);
break;
}
}
}
EndEnumFilters
}
}
return (DefWindowProc(hwnd, uMsg, wParam, lParam));
}
void dsr_handleNotifyEvents()
{
// char s[256];
for (;;) {
long evCode, param1, param2;
HRESULT h = s_pMediaEventEx->GetEvent(&evCode, &param1, &param2, 0);
if (FAILED(h)) break;
switch(evCode) {
case EC_BUFFERING_DATA:
{
// sprintf(s, "Handling Event: EC_BUFFERING_DATA: %d\n", param1);
// OutputDebugString(s);
s_buffering=param1;
if(!s_buffering)
{
lastfn_status[0]=0;
s_bufferstat=0;
PostMessage(mod.hMainWindow,WM_USER,0,243);
break;
}
}
break;
case EC_COMPLETE:
{
// OutputDebugString("Handling Event: EC_COMPLETE\n");
PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0);
}
default:
{
// sprintf(s, "Handling Event: 0x%x : param1=0x%x; param2 = 0x%x\n", evCode, param1, param2);
// OutputDebugString(s);
}
}
s_pMediaEventEx->FreeEventParams(evCode, param1, param2);
}
}
// Subclass of the Window containing the code. (Used to check if the parent got resized)
LRESULT CALLBACK dsr_SubclassParentWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT retval = CallWindowProc(s_OriginalVideoWndProc, hwnd, uMsg, wParam, lParam);
if ((uMsg == WM_SIZE) && (s_hVideoWnd))
{
RECT grc;
GetClientRect(s_hVideoWnd, &grc);
if (!EqualRect(&grc, &s_parentRect))
{
s_parentRect = grc;
if ((s_pBasicVideo) && (s_pVideoWindow))
{
long videoWidth, videoHeight;
if (SUCCEEDED(s_pBasicVideo->GetVideoSize(&videoWidth, &videoHeight)))
{
RECT r = fitMediaToWindow(grc, videoWidth, videoHeight);
s_pVideoWindow->SetWindowPosition(r.left, r.top, r.right-r.left, r.bottom-r.top);
}
}
}
}
if ((uMsg == WM_PARENTNOTIFY) && (LOWORD(wParam)==WM_RBUTTONDOWN))
{
SendStatus(WINAMPX_STATUS_VIDEO_RIGHT_CLICK, lParam);
}
if ((uMsg == WM_PARENTNOTIFY) && (LOWORD(wParam)==WM_LBUTTONDOWN))
{
static DWORD dwLastTime = 0;
int dwTimeNow = GetTickCount();
if (((dwTimeNow - dwLastTime) < GetDoubleClickTime()) && (dwLastTime != 0))
{
PostMessage(hwnd, WM_USER_DSR_LBUTTONDBLCLK, 0, 0); // Notify the video window that we double clicked
dwLastTime = 0;
}
else
dwLastTime = dwTimeNow;
}
if ((uMsg == WM_USER_DSR_FULLSCREEN) && (s_pVideoWindow))
{
s_pVideoWindow->HideCursor(wParam ? OATRUE : OAFALSE);
}
return retval;
}
#endif // WINAMPX