Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

1489 lines
39 KiB

//==========================================================================;
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
#include <streams.h>
#include <tchar.h>
#include <commctrl.h>
#include <initguid.h>
#include <stdio.h>
#include <wxdebug.h>
#include <ks.h>
#include <ksmedia.h>
#include "scope.h"
#include "resource.h"
//
//
// What this sample illustrates
//
// An audio oscilloscope that shows the waveform graphically as the audio is
// received by the filter. The filter is a renderer that can put where ever
// the normal runtime renderer goes. We have a single input pin that accepts
// a number of difference audio formats and renders the data as appropriate.
//
//
// Summary
//
// This is an audio oscilloscope renderer - we are basicly an audio renderer
// When we are created we also create a class to look after the scope window
// whose constructor creates a worker thread, when it is destroyed it will
// also terminate the worker thread. On that worker thread a window is looked
// after that shows the audio waveform for data sent to us. The data is kept
// in a circular buffer that loops when sufficient data has been received. We
// support a number of different audio formats such as 8bit mode and stereo.
//
//
// Demonstration Instructions
//
// (To really sure of this demonstration the machine must have a sound card)
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to
// the filters in the graph and find the filter (box) titled "Audio Renderer"
// This is the filter we will be replacing with this oscilloscope renderer.
// Then click on the box and hit DELETE. After that go to the Graph menu and
// select "Insert Filters", from the dialog box that pops up find and select
// "Oscilloscope", then dismiss the dialog. Back in the graph layout find the
// output pin of the filter that was connected to the input of the audio
// renderer you just deleted, right click and select "Render". You should
// see it being connected to the input pin of the oscilloscope you inserted
//
// Click Run on GRAPHEDT and you'll see a waveform for the audio soundtrack...
//
//
// Files
//
// icon1.ico The icon for the oscilloscope window
// makefile How we build it...
// resource.h Microsoft Visual C++ generated file
// scope.cpp The main filter and window implementations
// scope.def What APIs the DLL imports and exports
// scope.h Window and filter class definitions
// scope.mak Visual C++ generated makefile
// scope.rc Dialog box template for our window
// scope.reg What goes in the registry to make us work
//
//
// Base classes we use
//
// CBaseInputPin A generic input pin we use for the filter
// CCritSec A wrapper class around a critical section
// CBaseFilter The generic ActiveMovie filter object
//
//
// Setup data
const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
&KSDATAFORMAT_TYPE_VBI, // Major type
&KSDATAFORMAT_SUBTYPE_RAW8 // Minor type
};
const AMOVIESETUP_PIN sudPins =
{
L"Input", // Pin string name
FALSE, // Is it rendered
FALSE, // Is it an output
FALSE, // Allowed zero pins
FALSE, // Allowed many
&CLSID_NULL, // Connects to filter
L"Output", // Connects to pin
1, // Number of pins types
&sudPinTypes } ; // Pin information
const AMOVIESETUP_FILTER sudScope =
{
&CLSID_VBISCOPE, // Filter CLSID
L"VBI Scope", // String name
MERIT_DO_NOT_USE, // Filter merit
1, // Number pins
&sudPins // Pin details
};
// List of class IDs and creator functions for class factory
CFactoryTemplate g_Templates [] = {
{ L"VBI Scope"
, &CLSID_VBISCOPE
, CScopeFilter::CreateInstance
, NULL
, &sudScope }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
//
// CreateInstance
//
// This goes in the factory template table to create new instances
//
CUnknown * WINAPI CScopeFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
return new CScopeFilter(pUnk, phr);
} // CreateInstance
//
// Constructor
//
// Create the filter, scope window, and input pin
//
#pragma warning(disable:4355)
//
CScopeFilter::CScopeFilter(LPUNKNOWN pUnk,HRESULT *phr) :
CBaseFilter(NAME("VBIScope"), pUnk, (CCritSec *) this, CLSID_VBISCOPE),
m_Window(NAME("VBIScope"), this, phr)
{
// Create the single input pin
m_pInputPin = new CScopeInputPin(this,phr,L"Scope Input Pin");
if (m_pInputPin == NULL) {
*phr = E_OUTOFMEMORY;
}
} // (Constructor)
//
// Destructor
//
CScopeFilter::~CScopeFilter()
{
// Delete the contained interfaces
ASSERT(m_pInputPin);
delete m_pInputPin;
m_pInputPin = NULL;
} // (Destructor)
//
// GetPinCount
//
// Return the number of input pins we support
//
int CScopeFilter::GetPinCount()
{
return 1;
} // GetPinCount
//
// GetPin
//
// Return our single input pin - not addrefed
//
CBasePin *CScopeFilter::GetPin(int n)
{
// We only support one input pin and it is numbered zero
ASSERT(n == 0);
if (n != 0) {
return NULL;
}
return m_pInputPin;
} // GetPin
//
// JoinFilterGraph
//
// Show our window when we join a filter graph
// - and hide it when we are annexed from it
//
STDMETHODIMP CScopeFilter::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName)
{
HRESULT hr = CBaseFilter::JoinFilterGraph(pGraph, pName);
if (FAILED(hr)) {
return hr;
}
// Hide or show the scope as appropriate
if (pGraph == NULL) {
m_Window.InactivateWindow();
} else {
m_Window.ActivateWindow();
}
return hr;
} // JoinFilterGraph
//
// Stop
//
// Switch the filter into stopped mode.
//
STDMETHODIMP CScopeFilter::Stop()
{
CAutoLock lock(this);
if (m_State != State_Stopped) {
// Pause the device if we were running
if (m_State == State_Running) {
HRESULT hr = Pause();
if (FAILED(hr)) {
return hr;
}
}
DbgLog((LOG_TRACE,1,TEXT("Stopping....")));
// Base class changes state and tells pin to go to inactive
// the pin Inactive method will decommit our allocator which
// we need to do before closing the device
HRESULT hr = CBaseFilter::Stop();
if (FAILED(hr)) {
return hr;
}
}
return NOERROR;
} // Stop
//
// Pause
//
// Override Pause to stop the window streaming
//
STDMETHODIMP CScopeFilter::Pause()
{
CAutoLock lock(this);
// Check we can PAUSE given our current state
if (m_State == State_Running) {
m_Window.StopStreaming();
}
// tell the pin to go inactive and change state
return CBaseFilter::Pause();
} // Pause
//
// Run
//
// Overriden to start the window streaming
//
STDMETHODIMP CScopeFilter::Run(REFERENCE_TIME tStart)
{
CAutoLock lock(this);
HRESULT hr = NOERROR;
FILTER_STATE fsOld = m_State;
// This will call Pause if currently stopped
hr = CBaseFilter::Run(tStart);
if (FAILED(hr)) {
return hr;
}
m_Window.ActivateWindow();
if (fsOld != State_Running) {
m_Window.StartStreaming();
}
return NOERROR;
} // Run
//
// Constructor
//
CScopeInputPin::CScopeInputPin(CScopeFilter *pFilter,
HRESULT *phr,
LPCWSTR pPinName) :
CBaseInputPin(NAME("Scope Input Pin"), pFilter, pFilter, phr, pPinName)
{
m_pFilter = pFilter;
} // (Constructor)
//
// Destructor does nothing
//
CScopeInputPin::~CScopeInputPin()
{
} // (Destructor)
//
// BreakConnect
//
// This is called when a connection or an attempted connection is terminated
// and allows us to reset the connection media type to be invalid so that
// we can always use that to determine whether we are connected or not. We
// leave the format block alone as it will be reallocated if we get another
// connection or alternatively be deleted if the filter is finally released
//
HRESULT CScopeInputPin::BreakConnect()
{
// Check we have a valid connection
if (m_mt.IsValid() == FALSE) {
return E_FAIL;
}
m_pFilter->Stop();
// Reset the CLSIDs of the connected media type
m_mt.SetType(&GUID_NULL);
m_mt.SetSubtype(&GUID_NULL);
return CBaseInputPin::BreakConnect();
} // BreakConnect
//
// CheckMediaType
//
// Check that we can support a given proposed type
//
HRESULT CScopeInputPin::CheckMediaType(const CMediaType *pmt)
{
PKS_VBIINFOHEADER pIH = (PKS_VBIINFOHEADER) pmt->Format();
if (pIH == NULL)
return E_INVALIDARG;
if (pmt->majortype != KSDATAFORMAT_TYPE_VBI) {
return E_INVALIDARG;
}
if (pmt->subtype != KSDATAFORMAT_SUBTYPE_RAW8) {
return E_INVALIDARG;
}
if (pmt->formattype != KSDATAFORMAT_SPECIFIER_VBI) {
return E_INVALIDARG;
}
return NOERROR;
} // CheckMediaType
//
// SetMediaType
//
// Actually set the format of the input pin
//
HRESULT CScopeInputPin::SetMediaType(const CMediaType *pmt)
{
CAutoLock lock(m_pFilter);
// Pass the call up to my base class
HRESULT hr = CBaseInputPin::SetMediaType(pmt);
if (SUCCEEDED(hr)) {
PKS_VBIINFOHEADER pIH = (PKS_VBIINFOHEADER) pmt->Format();
// Save a copy of the VBI info header
m_pFilter->m_Window.m_VBIIH = *pIH;
ASSERT (pIH->BufferSize == ((pIH->EndLine - pIH->StartLine + 1) *
pIH->StrideInBytes));
m_pFilter->m_Window.m_nSamplesPerLine = pIH->SamplesPerLine;
m_pFilter->m_Window.m_MaxValue = 256;
m_pFilter->m_Window.m_DurationPerSample = 1.0 / pIH->SamplingFrequency;
m_pFilter->m_Window.m_DurationOfLine = (double) pIH->SamplesPerLine *
m_pFilter->m_Window.m_DurationPerSample;
if (!m_pFilter->m_Window.AllocWaveBuffers ())
return E_FAIL;
// Reset the horizontal scroll bar
m_pFilter->m_Window.SetHorizScrollRange(m_pFilter->m_Window.m_hwndDlg);
// Reset the horizontal scroll bar
m_pFilter->m_Window.SetControlRanges(m_pFilter->m_Window.m_hwndDlg);
}
return hr;
} // SetMediaType
//
// Active
//
// Implements the remaining IMemInputPin virtual methods
//
HRESULT CScopeInputPin::Active(void)
{
return NOERROR;
} // Active
//
// Inactive
//
// Called when the filter is stopped
//
HRESULT CScopeInputPin::Inactive(void)
{
return NOERROR;
} // Inactive
//
// Receive
//
// Here's the next block of data from the stream
//
HRESULT CScopeInputPin::Receive(IMediaSample * pSample)
{
// Lock this with the filter-wide lock
CAutoLock lock(m_pFilter);
// If we're stopped, then reject this call
// (the filter graph may be in mid-change)
if (m_pFilter->m_State == State_Stopped) {
return E_FAIL;
}
// Check all is well with the base class
HRESULT hr = CBaseInputPin::Receive(pSample);
if (FAILED(hr)) {
return hr;
}
// Send the sample to the video window object for rendering
return m_pFilter->m_Window.Receive(pSample);
} // Receive
//
// CScopeWindow Constructor
//
CScopeWindow::CScopeWindow(TCHAR *pName, CScopeFilter *pRenderer,HRESULT *phr) :
m_hInstance(g_hInst),
m_pRenderer(pRenderer),
m_hThread(INVALID_HANDLE_VALUE),
m_ThreadID(0),
m_hwndDlg(NULL),
m_hwnd(NULL),
m_pPoints1(NULL),
m_pPoints2(NULL),
m_nPoints(0),
m_bStreaming(FALSE),
m_bActivated(FALSE),
m_LastMediaSampleSize(0)
{
// Create a thread to look after the window
ASSERT(m_pRenderer);
m_hThread = CreateThread(NULL, // Security attributes
(DWORD) 0, // Initial stack size
WindowMessageLoop, // Thread start address
(LPVOID) this, // Thread parameter
(DWORD) 0, // Creation flags
&m_ThreadID); // Thread identifier
// If we couldn't create a thread the whole thing's off
ASSERT(m_hThread);
if (m_hThread == NULL) {
*phr = E_FAIL;
return;
}
// Wait until the window has been initialised
m_SyncWorker.Wait();
} // (Constructor)
//
// Destructor
//
CScopeWindow::~CScopeWindow()
{
// Ensure we stop streaming and release any samples
StopStreaming();
InactivateWindow();
// Tell the thread to destroy the window
SendMessage(m_hwndDlg, WM_GOODBYE, (WPARAM)0, (LPARAM)0);
// Make sure it has finished
ASSERT(m_hThread != NULL);
WaitForSingleObject(m_hThread,INFINITE);
CloseHandle(m_hThread);
if (m_pPoints1 != NULL) delete [] m_pPoints1;
if (m_pPoints2 != NULL) delete [] m_pPoints2;
} // (Destructor)
//
// ResetStreamingTimes
//
// This resets the latest sample stream times
//
HRESULT CScopeWindow::ResetStreamingTimes()
{
m_StartSample = 0;
m_EndSample = 0;
return NOERROR;
} // ResetStreamingTimes
//
// StartStreaming
//
// This is called when we start running state
//
HRESULT CScopeWindow::StartStreaming()
{
CAutoLock cAutoLock(this);
// Are we already streaming
if (m_bStreaming == TRUE) {
return NOERROR;
}
m_bStreaming = TRUE;
m_CurrentFrame = 0;
m_LastFrame = 0;
m_DroppedFrames = 0;
return NOERROR;
} // StartStreaming
//
// StopStreaming
//
// This is called when we stop streaming
//
HRESULT CScopeWindow::StopStreaming()
{
CAutoLock cAutoLock(this);
// Have we been stopped already
if (m_bStreaming == FALSE) {
return NOERROR;
}
m_bStreaming = FALSE;
return NOERROR;
} // StopStreaming
//
// InactivateWindow
//
// Called at the end to put the window in an inactive state
//
HRESULT CScopeWindow::InactivateWindow()
{
// Has the window been activated
if (m_bActivated == FALSE) {
return S_FALSE;
}
// Now hide the scope window
ShowWindow(m_hwndDlg,SW_HIDE);
m_bActivated = FALSE;
return NOERROR;
} // InactivateWindow
//
// ActivateWindow
//
// Show the scope window
//
HRESULT CScopeWindow::ActivateWindow()
{
// Has the window been activated
if (m_bActivated == TRUE) {
return S_FALSE;
}
m_bActivated = TRUE;
ASSERT(m_bStreaming == FALSE);
ShowWindow(m_hwndDlg,SW_SHOWNORMAL);
return NOERROR;
} // ActivateWindow
//
// OnClose
//
// This function handles the WM_CLOSE message
//
BOOL CScopeWindow::OnClose()
{
InactivateWindow();
return TRUE;
} // OnClose
typedef struct TBEntry_tag {
double TBDuration;
TCHAR TBText[16];
} TBEntry;
TBEntry Timebases[] =
{
1e-6, TEXT ("100 nS/Div"),
2e-6, TEXT ("200 nS/Div"),
5e-6, TEXT ("500 nS/Div"),
10e-6, TEXT ("1 uS/Div"),
20e-6, TEXT ("2 uS/Div"),
50e-6, TEXT ("5 uS/Div"),
60e-6, TEXT ("6 uS/Div"),
100e-6, TEXT ("10 uS/Div"),
};
#define N_TIMEBASES (sizeof(Timebases) / sizeof (Timebases[0]))
#define TIMEBASE_DEFAULT_INDEX 5
//
// SetControlRanges
//
// Set the scroll ranges for all of the vertical trackbars
//
void CScopeWindow::SetControlRanges(HWND hDlg)
{
SendMessage(m_hwndTopLine, TBM_SETRANGE, TRUE,
MAKELONG(m_VBIIH.StartLine, m_VBIIH.EndLine) );
SendMessage(m_hwndTopLine, TBM_SETPOS, TRUE, (LPARAM) m_TopLine);
SetDlgItemInt (hDlg, IDC_TOP_LINE_TEXT, m_TopLine, TRUE);
SendMessage(m_hwndBottomLine, TBM_SETRANGE, TRUE,
MAKELONG(m_VBIIH.StartLine, m_VBIIH.EndLine) );
SendMessage(m_hwndBottomLine, TBM_SETPOS, TRUE, (LPARAM) m_BottomLine);
SetDlgItemInt(hDlg, IDC_BOTTOM_LINE_TEXT, m_BottomLine, TRUE);
SendMessage(m_hwndTimebase, TBM_SETRANGE, TRUE, MAKELONG(0, N_TIMEBASES - 1) );
SendMessage(m_hwndTimebase, TBM_SETPOS, TRUE, (LPARAM) m_nTimebase);
SetDlgItemText (hDlg, IDC_TIMEBASE_TEXT, Timebases[m_nTimebase].TBText);
} // SetControlRanges
//
// StringFromTVStandard
//
TCHAR * StringFromTVStandard(long TVStd)
{
TCHAR * ptc;
switch (TVStd) {
case 0: ptc = TEXT("None"); break;
case AnalogVideo_NTSC_M: ptc = TEXT("NTSC_M"); break;
case AnalogVideo_NTSC_M_J: ptc = TEXT("NTSC_M_J"); break;
case AnalogVideo_NTSC_433: ptc = TEXT("NTSC_433"); break;
case AnalogVideo_PAL_B: ptc = TEXT("PAL_B"); break;
case AnalogVideo_PAL_D: ptc = TEXT("PAL_D"); break;
case AnalogVideo_PAL_G: ptc = TEXT("PAL_G"); break;
case AnalogVideo_PAL_H: ptc = TEXT("PAL_H"); break;
case AnalogVideo_PAL_I: ptc = TEXT("PAL_I"); break;
case AnalogVideo_PAL_M: ptc = TEXT("PAL_M"); break;
case AnalogVideo_PAL_N: ptc = TEXT("PAL_N"); break;
case AnalogVideo_PAL_60: ptc = TEXT("PAL_60"); break;
case AnalogVideo_SECAM_B: ptc = TEXT("SECAM_B"); break;
case AnalogVideo_SECAM_D: ptc = TEXT("SECAM_D"); break;
case AnalogVideo_SECAM_G: ptc = TEXT("SECAM_G"); break;
case AnalogVideo_SECAM_H: ptc = TEXT("SECAM_H"); break;
case AnalogVideo_SECAM_K: ptc = TEXT("SECAM_K"); break;
case AnalogVideo_SECAM_K1: ptc = TEXT("SECAM_K1"); break;
case AnalogVideo_SECAM_L: ptc = TEXT("SECAM_L"); break;
case AnalogVideo_SECAM_L1: ptc = TEXT("SECAM_L1"); break;
default:
ptc = TEXT("[undefined]");
break;
}
return ptc;
}
//
// SetHorizScrollRange
//
// The horizontal scrollbar handles scrolling through the buffer
//
void CScopeWindow::SetHorizScrollRange(HWND hDlg)
{
SendMessage(m_hwndTBScroll, TBM_SETRANGE, TRUE, MAKELONG(0, (m_nPoints - 1)));
SendMessage(m_hwndTBScroll, TBM_SETPOS, TRUE, (LPARAM) 0);
m_TBScroll = 0;
// TODO: Show info about each sample
TCHAR szFormat[160];
_stprintf (szFormat, TEXT("BpL: %d\nStr: %d\n%f\n"), m_VBIIH.SamplesPerLine,
m_VBIIH.StrideInBytes,
(float) m_VBIIH.SamplingFrequency / 1e6);
TCHAR * pStd = StringFromTVStandard (m_VBIIH.VideoStandard);
_tcscat (szFormat, pStd ? pStd : TEXT("[undefined]") );
SetDlgItemText (hDlg, IDC_FORMAT, szFormat);
} // SetHorizScrollRange
//
// ProcessHorizScrollCommands
//
// Called when we get a horizontal scroll bar message
//
void CScopeWindow::ProcessHorizScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
int pos;
int command = LOWORD (wParam);
if (command != TB_ENDTRACK &&
command != TB_THUMBTRACK &&
command != TB_LINEDOWN &&
command != TB_LINEUP &&
command != TB_PAGEUP &&
command != TB_PAGEDOWN)
return;
ASSERT (IsWindow ((HWND) lParam));
pos = (int) SendMessage((HWND) lParam, TBM_GETPOS, 0, 0L);
if ((HWND) lParam == m_hwndTBScroll) {
m_TBScroll = pos;
}
OnPaint();
} // ProcessHorizScrollCommands
//
// ProcessVertScrollCommands
//
// Called when we get a vertical scroll bar message
//
void CScopeWindow::ProcessVertScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
int pos;
int command = LOWORD (wParam);
if (command != TB_ENDTRACK &&
command != TB_THUMBTRACK &&
command != TB_LINEDOWN &&
command != TB_LINEUP &&
command != TB_PAGEUP &&
command != TB_PAGEDOWN)
return;
ASSERT (IsWindow ((HWND) lParam));
pos = (int) SendMessage((HWND) lParam, TBM_GETPOS, 0, 0L);
if ((HWND) lParam == m_hwndTopLine) {
m_TopLine = pos;
SetDlgItemInt (hDlg, IDC_TOP_LINE_TEXT, m_TopLine, TRUE);
} else if ((HWND) lParam == m_hwndBottomLine) {
m_BottomLine = pos;
SetDlgItemInt (hDlg, IDC_BOTTOM_LINE_TEXT, m_BottomLine, TRUE);
} else if ((HWND) lParam == m_hwndTimebase) {
m_nTimebase = pos ;
SetDlgItemText (hDlg, IDC_TIMEBASE_TEXT, Timebases[m_nTimebase].TBText);
}
OnPaint();
} // ProcessVertScrollCommands
//
// InitialiseWindow
//
// This is called by the worker window thread after it has created the main
// window and it wants to initialise the rest of the owner objects window
// variables such as the device contexts. We execute this function with the
// critical section still locked.
//
HRESULT CScopeWindow::InitialiseWindow(HWND hDlg)
{
HRESULT hr = NO_ERROR;
RECT rR;
// Initialise the window variables
m_hwnd = GetDlgItem (hDlg, IDC_SCOPEWINDOW);
// Quick sanity check
ASSERT(m_hwnd != NULL);
m_nTimebase = TIMEBASE_DEFAULT_INDEX;
m_fFreeze = 0;
// Default is show CC on the bottom trace
m_TopLine = 20;
m_BottomLine = 21;
m_TBScroll = 0;
m_TopF1 = TRUE;
m_TopF2 = TRUE;
m_BottomF1 = TRUE;
m_BottomF2 = FALSE;
GetWindowRect (m_hwnd, &rR);
m_Width = rR.right - rR.left;
m_Height = rR.bottom - rR.top;
m_hwndTopLine = GetDlgItem (hDlg, IDC_TOP_LINE);
m_hwndTopLineText = GetDlgItem (hDlg, IDC_TOP_LINE_TEXT);
m_hwndBottomLine = GetDlgItem (hDlg, IDC_BOTTOM_LINE);
m_hwndBottomLineText = GetDlgItem (hDlg, IDC_BOTTOM_LINE_TEXT);
m_hwndTimebase = GetDlgItem (hDlg, IDC_TIMEBASE);
m_hwndFreeze = GetDlgItem (hDlg, IDC_FREEZE);
m_hwndTBStart = GetDlgItem (hDlg, IDC_TS_START);
m_hwndTBEnd = GetDlgItem (hDlg, IDC_TS_LAST);
m_hwndFrameCount = GetDlgItem (hDlg, IDC_FRAMES);
m_hwndTBScroll = GetDlgItem (hDlg, IDC_TB_SCROLL);
SetControlRanges(hDlg);
SetHorizScrollRange(hDlg);
CheckDlgButton(hDlg, IDC_TOP_F1, m_TopF1);
CheckDlgButton(hDlg, IDC_TOP_F2, m_TopF2);
CheckDlgButton(hDlg, IDC_BOTTOM_F1, m_BottomF1);
CheckDlgButton(hDlg, IDC_BOTTOM_F2, m_BottomF2);
CheckDlgButton(hDlg, IDC_FREEZE, m_fFreeze);
m_hPen1 = CreatePen (PS_SOLID, 0, RGB (0, 0xff, 0));
m_hPen2 = CreatePen (PS_SOLID, 0, RGB (0, 0xff, 0));
m_hPenTicks = CreatePen (PS_SOLID, 0, RGB (0x80, 0x80, 0x80));
m_hBrushBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
if ( !m_hPen1 || !m_hPen2 || !m_hPenTicks || !m_hBrushBackground )
hr = E_FAIL;
HDC hdc = GetDC (NULL);
if ( hdc )
{
m_hBitmap = CreateCompatibleBitmap (hdc, m_Width, m_Height);
ReleaseDC (NULL, hdc);
}
else
{
m_hBitmap = NULL;
hr = E_FAIL;
}
return hr;
} // InitialiseWindow
//
// UninitialiseWindow
//
// This is called by the worker window thread when it receives a WM_GOODBYE
// message from the window object destructor to delete all the resources we
// allocated during initialisation
//
HRESULT CScopeWindow::UninitialiseWindow()
{
// Reset the window variables
DeleteObject (m_hPen1);
DeleteObject (m_hPen2);
DeleteObject (m_hPenTicks);
DeleteObject (m_hBitmap);
m_hwnd = NULL;
return NOERROR;
} // UninitialiseWindow
//
// ScopeDlgProc
//
// The Scope window is actually a dialog box, and this is its window proc.
// The only thing tricky about this is that the "this" pointer to the
// CScopeWindow is passed during the WM_INITDIALOG message and is stored
// in the window user data. This lets us access methods in the class
// from within the dialog.
//
INT_PTR CALLBACK ScopeDlgProc(HWND hDlg, // Handle of dialog box
UINT uMsg, // Message identifier
WPARAM wParam, // First message parameter
LPARAM lParam) // Second message parameter
{
CScopeWindow *pScopeWindow; // Pointer to the owning object
// Get the window long that holds our owner pointer
pScopeWindow = (CScopeWindow *) GetWindowLongPtr(hDlg, GWLP_USERDATA);
switch (uMsg) {
case WM_INITDIALOG:
pScopeWindow = (CScopeWindow *) lParam;
SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)pScopeWindow);
return TRUE;
case WM_COMMAND:
switch (wParam) {
case IDOK:
case IDCANCEL:
EndDialog (hDlg, 0);
return TRUE;
case IDC_FREEZE:
pScopeWindow->m_fFreeze =
(BOOL) IsDlgButtonChecked(hDlg,IDC_FREEZE);
pScopeWindow->DrawWaveform();
break;
case IDC_TOP_F1:
pScopeWindow->m_TopF1 =
(BOOL) IsDlgButtonChecked(hDlg,IDC_TOP_F1);
break;
case IDC_TOP_F2:
pScopeWindow->m_TopF2 =
(BOOL) IsDlgButtonChecked(hDlg,IDC_TOP_F2);
break;
case IDC_BOTTOM_F1:
pScopeWindow->m_BottomF1 =
(BOOL) IsDlgButtonChecked(hDlg,IDC_BOTTOM_F1);
break;
case IDC_BOTTOM_F2:
pScopeWindow->m_BottomF2 =
(BOOL) IsDlgButtonChecked(hDlg,IDC_BOTTOM_F2);
break;
default:
break;
}
case WM_VSCROLL:
pScopeWindow->ProcessVertScrollCommands(hDlg, wParam, lParam);
break;
case WM_HSCROLL:
pScopeWindow->ProcessHorizScrollCommands(hDlg, wParam, lParam);
break;
case WM_PAINT:
ASSERT(pScopeWindow != NULL);
pScopeWindow->OnPaint();
break;
// We stop WM_CLOSE messages going any further by intercepting them
// and then setting an abort signal flag in the owning renderer so
// that it knows the user wants to quit. The renderer can then
// go about deleting it's interfaces and the window helper object
// which will eventually cause a WM_DESTROY message to arrive. To
// make it look as though the window has been immediately closed
// we hide it and then wait for the renderer to catch us up
case WM_CLOSE:
ASSERT(pScopeWindow != NULL);
pScopeWindow->OnClose();
return (LRESULT) 0;
// We receive a WM_GOODBYE window message (synchronously) from the
// window object destructor in which case we do actually destroy
// the window and complete the process in the WM_DESTROY message
case WM_GOODBYE:
ASSERT(pScopeWindow != NULL);
pScopeWindow->UninitialiseWindow();
PostQuitMessage(FALSE);
EndDialog (hDlg, 0);
return (LRESULT) 0;
default:
break;
}
return (LRESULT) 0;
} // ScopeDlgProc
//
// MessageLoop
//
// This is the standard windows message loop for our worker thread. It sits
// in a normal processing loop dispatching messages until it receives a quit
// message, which may be generated through the owning object's destructor
//
HRESULT CScopeWindow::MessageLoop()
{
MSG Message; // Windows message structure
DWORD dwResult; // Wait return code value
HANDLE hWait[] = { (HANDLE) m_RenderEvent };
// Enter the modified message loop
while (TRUE) {
// We use this to wait for two different kinds of events, the first
// are the normal windows messages, the other is an event that will
// be signaled when a sample is ready
dwResult = MsgWaitForMultipleObjects((DWORD) 1, // Number events
hWait, // Event handle
FALSE, // Wait for either
INFINITE, // No timeout
QS_ALLINPUT); // All messages
// Has a sample become ready to render
if (dwResult == WAIT_OBJECT_0) {
DrawWaveform();
}
// Process the thread's window message
while (PeekMessage(&Message,NULL,(UINT) 0,(UINT) 0,PM_REMOVE)) {
// Check for the WM_QUIT message
if (Message.message == WM_QUIT) {
return NOERROR;
}
// Send the message to the window procedure
TranslateMessage(&Message);
DispatchMessage(&Message);
}
}
} // MessageLoop
//
// WindowMessageLoop
//
// This creates a window and processes it's messages on a separate thread
//
DWORD __stdcall CScopeWindow::WindowMessageLoop(LPVOID lpvThreadParm)
{
CScopeWindow *pScopeWindow; // The owner renderer object
// Cast the thread parameter to be our owner object
pScopeWindow = (CScopeWindow *) lpvThreadParm;
pScopeWindow->m_hwndDlg =
CreateDialogParam(
pScopeWindow->m_hInstance, // Handle of app instance
MAKEINTRESOURCE (IDD_SCOPEDIALOG), // Dialog box template
NULL, // Handle of owner window
ScopeDlgProc, // Address of dialog procedure
(LPARAM) pScopeWindow // Initialization value
);
if (pScopeWindow->m_hwndDlg )
{
// Initialise the window, then signal the constructor that it can
// continue and then unlock the object's critical section and
// process messages
if ( SUCCEEDED(pScopeWindow->InitialiseWindow(pScopeWindow->m_hwndDlg)) )
{
pScopeWindow->m_SyncWorker.Set();
pScopeWindow->MessageLoop();
}
}
ExitThread(TRUE);
return TRUE;
} // WindowMessageLoop
//
// OnPaint
//
// WM_PAINT message
//
BOOL CScopeWindow::OnPaint()
{
DrawWaveform();
return TRUE;
} // OnPaint
//
// ClearWindow
//
// Clear the scope to black and draw the center tickmarks
//
void CScopeWindow::ClearWindow(HDC hdc)
{
int y = m_Height / 2;
SetMapMode (hdc, MM_TEXT);
SetWindowOrgEx (hdc, 0, 0, NULL);
SetViewportOrgEx (hdc, 0, 0, NULL);
// Paint the entire window black
PatBlt(hdc, // Handle of device context
(INT) 0, // x-coord of upper-left corner
(INT) 0, // y-coord of upper-left corner
m_Width, // Width of rectangle to be filled
m_Height, // Height of rectangle to be filled
BLACKNESS); // Raster operation code
// Draw the horizontal line
HPEN hPenOld = (HPEN) SelectObject (hdc, m_hPenTicks);
MoveToEx (hdc, 0, y, NULL);
LineTo (hdc, m_Width, y);
// Draw the tickmarks
float inc = (float) m_Width / 10;
int pos, j;
int TickPoint;
for (j = 0; j <= 10; j++) {
if (j == 0 || j == 5 || j == 10)
TickPoint = m_Height / 15;
else
TickPoint = m_Height / 30;
pos = (int) (j * inc);
MoveToEx (hdc, pos, y + TickPoint, NULL);
LineTo (hdc, pos, y - TickPoint);
}
SelectObject (hdc, hPenOld);
} // ClearWindow
//
// DrawPartialWaveform
//
// Draw a part of the VBIScope waveform - IndexStart and IndexEnd
// are pointers into the m_pPoints array (in LOGICAL COORDINATES)
// while ViewpointStart and ViewpointEnd are in SCREEN COORDINATES
//
void CScopeWindow::DrawPartialWaveform(HDC hdc,
int IndexStart,
int IndexEnd,
int ViewportStart,
int ViewportEnd)
{
int nPoints = IndexEnd - IndexStart;
int nViewportWidth = ViewportEnd - ViewportStart;
HPEN OldPen;
ASSERT (IndexStart + nPoints < m_nPoints);
// Origin at lower left, x increases up, y increases to right
SetMapMode (hdc, MM_ANISOTROPIC);
// Draw the first trace
if (m_TopF1 || m_TopF2) {
SetWindowOrgEx (hdc, IndexStart, 0, NULL);
SetWindowExtEx (hdc, nPoints, (int) m_MaxValue, NULL);
SetViewportExtEx (hdc, nViewportWidth, -m_Height / 2, NULL);
SetViewportOrgEx (hdc, ViewportStart, m_Height / 2, NULL);
OldPen = (HPEN) SelectObject (hdc, m_hPen1);
Polyline (hdc, m_pPoints1 + IndexStart, nPoints + 1);
SelectObject (hdc, OldPen);
}
// Draw the second trace
if (m_BottomF1 || m_BottomF2) {
SetWindowOrgEx (hdc, IndexStart, 0, NULL);
SetWindowExtEx (hdc, nPoints, (int) m_MaxValue, NULL);
SetViewportExtEx (hdc, nViewportWidth, -m_Height / 2, NULL);
SetViewportOrgEx (hdc, ViewportStart, m_Height , NULL);
OldPen = (HPEN) SelectObject (hdc, m_hPen2);
Polyline (hdc, m_pPoints2 + IndexStart, nPoints + 1);
SelectObject (hdc, OldPen);
}
} // DrawPartialWaveform
//
// DrawWaveform
//
// Draw the full VBIScope waveform
//
void CScopeWindow::DrawWaveform(void)
{
CAutoLock lock(m_pRenderer);
TCHAR szT[40];
if (m_pPoints1 == NULL)
return;
double ActualLineStartTime = m_VBIIH.ActualLineStartTime / 100.0; // in microseconds.
double StartTime = m_TBScroll * m_DurationPerSample;
double StopTime = StartTime + Timebases [m_nTimebase].TBDuration;
double TotalLineTime = m_nPoints * m_DurationPerSample;
int PointsToDisplay;
int ActualPointsToDisplay;
ActualPointsToDisplay = PointsToDisplay = (int) (m_nPoints *
(Timebases [m_nTimebase].TBDuration / TotalLineTime));
if (m_TBScroll + PointsToDisplay >= m_nPoints - 1) {
ActualPointsToDisplay = m_nPoints - m_TBScroll - 1;
}
HDC hdc = GetWindowDC (m_hwnd); // WindowDC has clipping region
if ( hdc )
{
HDC hdcT = CreateCompatibleDC (hdc);
if ( hdcT )
{
HBITMAP hBitmapOld = (HBITMAP) SelectObject (hdcT, m_hBitmap);
ClearWindow (hdcT);
DrawPartialWaveform(hdcT,
m_TBScroll, m_TBScroll + ActualPointsToDisplay,// Index start, Index end
0, (int) (m_Width * (float) ActualPointsToDisplay / PointsToDisplay)); // Window start, Window end
SetMapMode (hdcT, MM_TEXT);
SetWindowOrgEx (hdcT, 0, 0, NULL);
SetViewportOrgEx (hdcT, 0, 0, NULL);
BitBlt(hdc, // Handle of destination device context
0, // x-coordinate of upper-left corner
0, // y-coordinate of upper-left corner
m_Width, // Wwidth of destination rectangle
m_Height, // Height of destination rectangle
hdcT, // Handle of source device context
0, // x-coordinate of source rectangle
0, // y-coordinate of source rectangle
SRCCOPY); // Raster operation code
SelectObject (hdcT, hBitmapOld);
DeleteDC (hdcT);
}
GdiFlush();
ReleaseDC (m_hwnd, hdc);
}
// Frame count
wsprintf (szT, TEXT("%ld"), m_CurrentFrame);
SetDlgItemText (m_hwndDlg, IDC_FRAMES, szT);
// Dropped
wsprintf (szT, TEXT("%ld"), m_DroppedFrames);
SetDlgItemText (m_hwndDlg, IDC_DROPPED, szT);
// Show the start time of the display in microseconds
_stprintf (szT, TEXT("%7.4lfus"), ActualLineStartTime + StartTime * 1e6);
SetDlgItemText (m_hwndDlg, IDC_TS_START, szT);
// And the stop time
_stprintf (szT, TEXT("%7.4lfus"), ActualLineStartTime + StopTime * 1e6);
SetDlgItemText (m_hwndDlg, IDC_TS_LAST, szT);
} // DrawWaveform
//
// AllocWaveBuffers
//
// Allocate two buffers for two video lines
// This is only called when the format changes
// Return TRUE if allocations succeed
//
BOOL CScopeWindow::AllocWaveBuffers()
{
int j;
if (m_pPoints1) delete [] m_pPoints1;
if (m_pPoints2) delete [] m_pPoints2;
m_pPoints1 = NULL;
m_pPoints2 = NULL;
m_nPoints = 0;
m_nPoints = m_nSamplesPerLine = m_VBIIH.SamplesPerLine;
if (m_pPoints1 = new POINT [m_nPoints]) {
for (j = 0; j < m_nPoints; j++)
m_pPoints1[j].x = j;
}
if (m_pPoints2 = new POINT [m_nPoints]) {
for (j = 0; j < m_nPoints; j++)
m_pPoints2[j].x = j;
}
// Return TRUE if allocations succeeded
ASSERT ((m_pPoints1 != NULL) && (m_pPoints2 != NULL));
return ((m_pPoints1 != NULL) && (m_pPoints2 != NULL));
} // AllocWaveBuffers
//
// CopyWaveform
//
// Copy the current MediaSample into a POINT array so we can use GDI
// to paint the waveform.
//
void CScopeWindow::CopyWaveform(IMediaSample *pMediaSample)
{
BYTE *pWave; // Pointer to VBI data
ULONG nBytes;
ULONG j;
BYTE *pb;
HRESULT hr;
REFERENCE_TIME tDummy;
IMediaSample2 *Sample2;
AM_SAMPLE2_PROPERTIES SampleProperties;
pMediaSample->GetPointer(&pWave);
ASSERT(pWave != NULL);
nBytes = pMediaSample->GetActualDataLength();
ASSERT (nBytes == (m_VBIIH.EndLine - m_VBIIH.StartLine + 1) *
m_VBIIH.StrideInBytes);
hr = pMediaSample->GetMediaTime (&m_CurrentFrame, &tDummy);
m_DroppedFrames += (m_CurrentFrame - (m_LastFrame + 1));
m_LastFrame = m_CurrentFrame;
// All this to just get the field polarity flags...
if (SUCCEEDED( pMediaSample->QueryInterface(
__uuidof(IMediaSample2),
reinterpret_cast<PVOID*>(&Sample2) ) )) {
hr = Sample2->GetProperties(
sizeof( SampleProperties ),
reinterpret_cast<PBYTE> (&SampleProperties) );
Sample2->Release();
m_FrameFlags = SampleProperties.dwTypeSpecificFlags;
}
m_IsF1 = (m_FrameFlags & KS_VIDEO_FLAG_FIELD1);
// Copy data for the top line
if ((m_IsF1 && m_TopF1) || (!m_IsF1 && m_TopF2)) {
pb = pWave + ((m_TopLine - m_VBIIH.StartLine) *
m_VBIIH.StrideInBytes);
for (j = 0; j < m_VBIIH.SamplesPerLine; j++) {
m_pPoints1[j].y = (int)*pb++;
}
}
// Copy data for the bottom line
if ((m_IsF1 && m_BottomF1) || (!m_IsF1 && m_BottomF2)) {
pb = pWave + ((m_BottomLine - m_VBIIH.StartLine) *
m_VBIIH.StrideInBytes);
for (j = 0; j < m_VBIIH.SamplesPerLine; j++) {
m_pPoints2[j].y = (int)*pb++;
}
}
} // CopyWaveform
//
// Receive
//
// Called when the input pin receives another sample.
// Copy the waveform to our circular 1 second buffer
//
HRESULT CScopeWindow::Receive(IMediaSample *pSample)
{
CAutoLock cAutoLock(this);
ASSERT(pSample != NULL);
// Has our UI been frozen
if (m_fFreeze) {
return NOERROR;
}
REFERENCE_TIME tStart, tStop;
pSample->GetTime (&tStart,&tStop);
m_StartSample = tStart;
m_EndSample = tStop;
// Ignore samples of the wrong size!!!
if ((m_LastMediaSampleSize = pSample->GetActualDataLength()) != (int) m_VBIIH.BufferSize) {
// ASSERT (FALSE);
return NOERROR;
}
if (m_bStreaming == TRUE) {
CopyWaveform (pSample); // Copy data to our circular buffer
SetEvent(m_RenderEvent); // Set an event to display the
}
return NOERROR;
} // Receive
//
// DllRegisterServer
//
// Handles DLL registry
//
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2( TRUE );
} // DllRegisterServer
//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2( FALSE );
} // DllUnregisterServer