I want to use PushSource filter to capture but I need to "connect" it to "videocap" sample. This filter is not a standalone filter, so it can't be enumerated as capture device, also I dont want it to convert like that
Well, I use Nvidia Encoder Filter that does not show up in GraphEdit. To do that I just define the GUID:
Const
CLSID_NVIDIA_VideoEncoderFilter : TGUID = '{B63E31D0-87B5-477f-B224-4A35B6BECED6}';
Then I create the filter in memory like that:
Var
N: IBaseFilter;
begin
CoCreateInstance(CLSID_NVIDIA_VideoEncoderFilter, Nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, N);
if Assigned(N) then
Begin
// connect to any filter that you can use in your system
End;
Also you can see this "hidden" filter in your GraphEdit now.
Edit: Here is the code that I link filters. It searches for every pin that may be connected to the next filter. This code connects audio out pins to audio in pins and video out pins to video in pins etc. It is very flexible.
Procedure Connect(Builder: IGraphBuilder; SourceFilter, DestFilter: IBaseFilter);
Var
SourceEnum, DestEnum: IEnumPins;
SourcePin, DestPin: IPin;
FI: _FilterInfo;
S, D: String;
Begin
SourceFilter.QueryFilterInfo(FI);
S := FI.achName;
DestFilter.QueryFilterInfo(FI);
D := FI.achName;
SourceFilter.EnumPins(SourceEnum);
DestFilter.EnumPins(DestEnum);
DestEnum.Reset;
While DestEnum.Next(1, DestPin, Nil) = S_OK Do
Begin
SourceEnum.Reset;
While SourceEnum.Next(1, SourcePin, Nil) = S_OK Do
If Builder.Connect(SourcePin, DestPin) = S_OK Then
Begin
SourceEnum := Nil;
DestEnum := Nil;
SourcePin := Nil;
DestPin := Nil;
Exit;
End;
End;
SourceEnum := Nil;
DestEnum := Nil;
SourcePin := Nil;
DestPin := Nil;
Raise Exception.Create(Format('There is no pin from %s to connect to %d', [S, D]));
End;
Now you can use this method with your filters as in the following code
Var
SourceFilter, DestFilter: IBaseFilter;
Builder: IGraphBuilder;
Begin
SourceFilter := SrcFilter As IBaseFilter;
DestFilter := DstFilter As IBaseFilter;
Builder := FilterGraph As IGraphBuilder;
Connect(Builder, SourceFilter, DestFilter);
End;
Hope this helps. We were all newbies once ;)
Related
I've a problem with using DirectX (DirectSound) on Windows 10. I'me changing some legacy code that used DirectX (DirectX 9 I think) and run on Windows XP.
Everything is still working great on Windows XP but I can't get a sound on Windows 10.
The application uses these files: DXUTIL.PAS (Original ObjectPascal conversion made by: Boris V.), lzexpand.pas (Author: Vadim Bodrov) and DirectSound.pas (DirectX 9.0 Delphi / FreePascal adaptation by Alexey Barkovoy).
procedure TForm1.Button1Click(Sender: TObject);
var
sndgwait : PSound;
begin
InitSB(Handle);
LoadWave(sndgwait, 'D:\game\EXP01.wav', 1);
StartSound(sndgwait, false);
end;
function LoadWave(var Sound: PSound; fn : string; conc : integer) : boolean;
var
cbData, cbdata1 : DWORD;
pd1 : pointer;
hfile : longint;
vreopenbuff : TOFStruct;
begin
hfile := LZOpenFile(PAnsiChar(fn), vreopenbuff, OF_READ);
if hfile < 0 then begin result := false; exit; end;
cbdata := LZSeek(hfile, 0, 2); // file size
LZSeek(hfile, 0, 0); //back to the start of the wav
getmem(pd1, cbdata);
LZRead(hfile, pd1, cbdata);
LZClose(hfile);
if conc < 1 then conc := 1;
result := ParseWaveData(Sound, conc, cbdata, pd1);
end;
function ParseWaveData(var Sound : PSound; conc : integer; cbdata : dword; pd1 : pointer) : boolean;
var
pWaveHeader: PWAVEFORMATEX;
pDSB: IDirectSoundBuffer;
dsBD: TDSBUFFERDESC;
rr : longint;
begin
if lpDS = nil then begin
result := false;
exit;
end;
Sound := PSNDOBJ(LocalAlloc(LPTR, SizeOf(TSNDOBJ) + (conc-1) * SizeOf(IDirectSoundBuffer)));
Sound^.iAlloc := conc;
Sound^.cbWaveSize := 0;
Sound^.pbWaveData := nil;
pwaveHeader := nil;
Sound^.pbData := pd1;
Sound^.cbSize := cbdata;
if DSParseWaveResource(pd1, pWaveHeader, Sound^.pbWaveData, Sound^.cbWaveSize) then begin
ZeroMemory(#dsBD, SizeOf(dsBD));
dsBD.dwSize := SizeOf(dsBD);
dsBD.dwFlags := DSBCAPS_STATIC or DSBCAPS_GETCURRENTPOSITION2 or DSBCAPS_CTRLFREQUENCY orDSBCAPS_CTRLPAN or DSBCAPS_CTRLVOLUME;//DSBCAPS_CTRLDEFAULT or
dsBD.lpwfxFormat := pWaveHeader;
dsBD.dwBufferBytes := Sound^.cbWaveSize;
if lpDS.CreateSoundBuffer(dsBD, pDSB, nil) = DS_OK then begin
if not DSFillSoundBuffer(pDSB, Sound^.pbWaveData, dsBD.dwBufferBytes) then begin
pDSB._Release;
pDSB := nil;
end;
Sound^.Buffers[0] := pDSB;
for rr := 1 to conc - 1 do begin
lpDS.DuplicateSoundBuffer(Sound^.Buffers[0], Sound^.Buffers[rr]);
end;
end else begin
pDSB := nil;
SndObjDestroy(Sound);
Sound := nil;
end;
end;
Result := Sound <> nil;
end;
function StartSound(Sound: PSound; Loop: boolean = false; waitforend: boolean = false): boolean;
begin
if Loop then
StartSound := SndObjPlay(Sound, DSBPLAY_LOOPING)
else
StartSound := SndObjPlay(Sound, 0);
if waitforend and not loop then
while SoundPlaying(Sound) do Application.ProcessMessages;
end;
function SndObjPlay(pSO: PSNDOBJ; dwPlayFlags: DWORD): Boolean;
var
pDSB: IDirectSoundBuffer;
begin
Result := FALSE;
if pSO = nil then
begin
exit;
end;
if ((dwPlayFlags and DSBPLAY_LOOPING) = 0) or (pSO^.iAlloc = 1) then
begin
pDSB := SndObjGetFreeBuffer(pSO);
if (pDSB <> nil) then
Result := SUCCEEDED(pDSB.Play(0, 0, dwPlayFlags));
end else
Result:= FALSE;
end;
All values in ParseWaveData call are correct I think (no nil values).
I'm not gething any errors. Just there is no sound.
What can be a problem here?
Or are there other ways to use DirectX for sound in this old app on windows 10? Any example would be great.
Thanks....
While I cannot guarantee this is the fix, I also cannot just comment because I don't have 50 rep, it is worth a try. I had a lot of issues with Windows 10 sound, often it seemed at random! Switching apps disabled sound from background apps, game programming wouldn't make a peep, even playing music to see if I had the right track with CD burning software was broken, Netflix breaking sound until I rebooted, wireless headphones not working or volume way too low. Infuriating. You might not be wrestling control from whatever has current priority because of an W10 April 2018 change and just not had the issues I've had.
Anyway, this is worth a try:
Right-click the Sound Icon in the bottom right of task bar. Click
Playback or Audio Devices. Right-click the speaker icon that appears
in the settings screen. Click Properties from the pop-up menu. Click
the Advanced tab on the Speakers Properties screen. Uncheck the boxes
for Allow applications to take exclusive control and Give exclusive
mode applications priority.
I've solved this. At the end it wasn't anything with DirectX.
hfile := LZOpenFile(PAnsiChar(fn), vreopenbuff, OF_READ);
Parameter "fn" (string) that was input to the procedure was wrong. Something messes up. The file name was incorrect, so file that should be played was empty. Nothing to do with DirectX. I've replaced above command with:
hfile := LZOpenFile(PAnsiChar(AnsiString(fn)), vreopenbuff, OF_READ);
Now it is working.
Thanks...
I am trying to drag and drop from VirtualTreeView to create a file in shell (drag and drop from VirtualTreeView to a folder in File Explorer or desktop folder).
I only found example of doing the opposite (shell to VirtualTreeView), but I cannot find any example for doing that. Help?
Doing any drag-drop operations in Windows involves creating an IDataObject, and giving that object to Windows.
The Virtual Treeview handles a lot of that grunt-work for you, creating an object that implements IDataObject for you. The tree then raises events when you need to help populate it.
When passing "file-like" things through a copy-paste or a drag-drop, you are require to add two clipboard formats to the IDataObject:
CF_FILEDESCRIPTOR, and
CF_FILECONTENTS
In addition to support for formats that the virtualtree itself will add, you can choose to indicate support for more clipboard format.
OnGetUserClipboardFormats Event
This is the event where you are given a chance to add additional clipboard formats to the IDataObject that the tree will be creating:
procedure TForm1.lvAttachmentsGetUserClipboardFormats(Sender: TBaseVirtualTree;
var Formats: TFormatEtcArray);
var
i: Integer;
begin
//Add formats for CF_FILEDESCRIPTOR and CF_FILECONTENTS
i := Length(Formats);
SetLength(Formats, i + 1);
Formats[i].cfFormat := CF_FILEDESCRIPTOR;
Formats[i].ptd := nil;
Formats[i].dwAspect := DVASPECT_CONTENT;
Formats[i].lindex := -1;
Formats[i].tymed := TYMED_HGLOBAL;
i := Length(Formats);
SetLength(Formats, i + 1);
Formats[i].cfFormat := CF_FILECONTENTS;
Formats[i].ptd := nil;
Formats[i].dwAspect := DVASPECT_CONTENT;
Formats[i].lindex := 0;
Formats[i].tymed := TYMED_ISTREAM;
end;
The tree will then given the IDataObject to the shell as part of the drag-drop operation.
Later, an application that the user dropped items onto will enumerate all formats in the IDataObject, e.g.:
CF_HTML ("HTML Format")
CFSTR_FILEDESCRIPTOR ("FileGroupDescriptorW")
CFSTR_FILECONTENTS ("FileContents")
CF_ENHMETAFILE
And it will see that the IDataObject contains FileDescriptor and FileContents.
The receiving application will then ask the IDataObject to actually cough up data. (This "delayed-rendering" is a good thing, it means your source application doesn't actually have to read any content unless it actually gets requested).
OnRenderOleData Event
This is the event where the virtual tree realizes its IDataObject has been asked to render something, and it needs you to finally render that actual content.
The general idea with these two clipboard formats is:
CF_FILEDESCRIPTOR lets you return a record that describes the file-like thing (e.g. filename, file size, created date, last modified date, last accessed date)
CF_FILECONTENTS lets you return an IStream that contains the actual file contents
procedure TForm1.lvAttachmentsRenderOLEData(Sender: TBaseVirtualTree; const FormatEtcIn: tagFORMATETC;
out Medium: tagSTGMEDIUM; ForClipboard: Boolean; var Result: HRESULT);
var
global: HGLOBAL;
stm: IStream;
begin
if FormatEtcIn.cfFormat = CF_FILEDESCRIPTOR then
begin
global := GetAttachmentFileDescriptorsFromListView(lvAttachments, ForClipboard);
if global = 0 then
Exit;
ZeroMemory(#Medium, SizeOf(Medium));
Medium.tymed := TYMED_HGLOBAL;
Medium.hGlobal := global;
Result := S_OK;
end
else if FormatEtcIn.cfFormat = CF_FILECONTENTS then
begin
ZeroMemory(#Medium, SizeOf(Medium));
Medium.tymed := TYMED_ISTREAM;
Result := GetAttachmentStreamFromListView(lvAttachments, ForClipboard, FormatEtcIn.lindex, stm);
if Failed(Result) then
Exit;
Medium.stm := Pointer(stm);
IUnknown(Medium.stm)._AddRef;
Result := S_OK;
end;
end;
The first helper function creates an array of FILE_DESCRIPTOR objects, and copies them to a HGLOBAL allocated memory:
function GetAttachmentFileDescriptorsFromListView(Source: TVirtualStringTree; ForClipboard: Boolean): HGLOBAL;
var
i: Integer;
nCount: Integer;
nodes: TNodeArray;
descriptors: TFileDescriptorDynArray;
data: TAttachment;
begin
Result := 0;
if ForClipboard then
nodes := Source.GetSortedCutCopySet(False)
else
nodes := Source.GetSortedSelection(False);
if Length(nodes) = 0 then
Exit;
nCount := 0;
for i := 0 to Length(nodes) - 1 do
begin
//Get the file thing from this node
data := GetNodeDataFromNode(nodes[i]);
if not Assigned(data) then
Continue;
//Increase the size of our descriptors array by one
Inc(nCount);
SetLength(Descriptors, nCount);
//Fill in the next descriptor
descriptors[nCount-1] := data.ToWindowsFileDescriptor;
end;
Result := FileDescriptorsToHGLOBAL(descriptors);
end;
The second helper copies your file-like thing's binary contents to an IStream:
function GetAttachmentStreamFromListView(Source: TVirtualStringTree; ForClipboard: Boolean; lindex: Integer; var stm: IStream): HResult;
var
nodes: TNodeArray;
data: TAttachment;
begin
Result := E_FAIL;
if ForClipboard then
nodes := Source.GetSortedCutCopySet(False)
else
nodes := Source.GetSortedSelection(False);
if Length(nodes) = 0 then
Exit;
if (lIndex < Low(Nodes)) or (lIndex > High(Nodes)) then
begin
Result := DV_E_LINDEX;
Exit;
end;
//Get the file thing from this node
data := GetNodeDataFromNode(nodes[i]);
if not Assigned(data) then
Continue;
//Fetch the content into a IStream wrapped memory stream
stm := data.GetStream(nil);
Result := S_OK;
end;
Your attachment object, whatever it is has to know:
how to represent itself as a TFileDescriptor
how to return the contents as an IStream
I'm trying to get stream video from USB Web-camera in Delphi XE. Found a working example, but I can't figure out how to get all supported video formats (resolution, color depth, etc). This example works well with fixed Mediatype:
var
Form1: TForm1;
IniFile: TIniFile;
DeviceName: OleVariant;
PropertyName: IPropertyBag;
pDevEnum: ICreateDEvEnum;
pEnum: IEnumMoniker;
pMoniker: IMoniker;
MArray1: array of IMoniker;
FGraphBuilder: IGraphBuilder;
FCaptureGraphBuilder: ICaptureGraphBuilder2;
FMux: IBaseFilter;
FSink: IFileSinkFilter;
FMediaControl: IMediaControl;
FVideoWindow: IVideoWindow;
FVideoCaptureFilter: IBaseFilter;
FAudioCaptureFilter: IBaseFilter;
FVideoRect: TRect;
FBaseFilter: IBaseFilter;
FSampleGrabber: ISampleGrabber;
MediaType: AM_MEDIA_TYPE;
function TForm1.Initializ: HResult;
begin
Result := CoCreateInstance(CLSID_SystemDeviceEnum, NIL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, pDevEnum);
if Result <> S_OK then
EXIT;
Result := pDevEnum.CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
pEnum, 0);
if Result <> S_OK then
EXIT;
setlength(MArray1, 0);
while (S_OK = pEnum.Next(1, pMoniker, Nil)) do
begin
setlength(MArray1, length(MArray1) + 1);
MArray1[length(MArray1) - 1] := pMoniker;
Result := pMoniker.BindToStorage(NIL, NIL, IPropertyBag, PropertyName);
if FAILED(Result) then
Continue;
Result := PropertyName.Read('FriendlyName', DeviceName, NIL);
if FAILED(Result) then
Continue;
ListBox1.Items.Add(DeviceName);
end;
if ListBox1.Count = 0 then
begin
ShowMessage('Webcam not found!');
Result := E_FAIL;
EXIT;
end;
ListBox1.ItemIndex := 0;
Result := S_OK;
end;
function TForm1.CreateGraph: HResult;
var
pConfigMux: IConfigAviMux;
begin
FVideoCaptureFilter := NIL;
FVideoWindow := NIL;
FMediaControl := NIL;
FSampleGrabber := NIL;
FBaseFilter := NIL;
FCaptureGraphBuilder := NIL;
FGraphBuilder := NIL;
Result := CoCreateInstance(CLSID_FilterGraph, NIL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, FGraphBuilder);
if FAILED(Result) then
EXIT;
Result := CoCreateInstance(CLSID_SampleGrabber, NIL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, FBaseFilter);
if FAILED(Result) then
EXIT;
Result := CoCreateInstance(CLSID_CaptureGraphBuilder2, NIL,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, FCaptureGraphBuilder);
if FAILED(Result) then
EXIT;
Result := FGraphBuilder.AddFilter(FBaseFilter, 'GRABBER');
if FAILED(Result) then
EXIT;
Result := FBaseFilter.QueryInterface(IID_ISampleGrabber, FSampleGrabber);
if FAILED(Result) then
EXIT;
if FSampleGrabber <> NIL then
begin
ZeroMemory(#MediaType, sizeof(AM_MEDIA_TYPE));
with MediaType do
begin
majortype := MEDIATYPE_Video;
subtype := MEDIASUBTYPE_RGB24;
formattype := FORMAT_VideoInfo;
end;
FSampleGrabber.SetMediaType(MediaType);
FSampleGrabber.SetBufferSamples(TRUE);
FSampleGrabber.SetOneShot(FALSE);
end;
Result := FCaptureGraphBuilder.SetFiltergraph(FGraphBuilder);
if FAILED(Result) then
EXIT;
if ListBox1.ItemIndex >= 0 then
begin
MArray1[ListBox1.ItemIndex].BindToObject(NIL, NIL, IID_IBaseFilter,
FVideoCaptureFilter);
FGraphBuilder.AddFilter(FVideoCaptureFilter, 'VideoCaptureFilter');
end;
Result := FCaptureGraphBuilder.RenderStream(#PIN_CATEGORY_PREVIEW, nil,
FVideoCaptureFilter, FBaseFilter, nil);
if FAILED(Result) then
EXIT;
Result := FGraphBuilder.QueryInterface(IID_IVideoWindow, FVideoWindow);
if FAILED(Result) then
EXIT;
FVideoWindow.put_WindowStyle(WS_CHILD or WS_CLIPSIBLINGS);
FVideoWindow.put_Owner(Panel1.Handle);
FVideoRect := Panel1.ClientRect;
FVideoWindow.SetWindowPosition(FVideoRect.Left, FVideoRect.Top,
FVideoRect.Right - FVideoRect.Left, FVideoRect.Bottom - FVideoRect.Top);
FVideoWindow.put_Visible(TRUE);
Result := FGraphBuilder.QueryInterface(IID_IMediaControl, FMediaControl);
if FAILED(Result) then
EXIT;
FMediaControl.Run();
end;
How can I get all webcam supported video formats to combobox? If I need something to add, please, post in comment, thanks.
The code snippet provided is not quite relevant and presumably is just what you have at the moment. To enumerate video formats you typically obtain an output pin of interest first, which you don't do in your code snippet and rely on RenderStream call to do dirty work for you.
Then, you obtain IAMStreamConfig interface pointer from the pin and enumerate the media types (formats). Alternatively you can enumerate using IPin::EnumMediaTypes, however IAMStreamConfig::GetStreamCaps is the canonical way, esp. for webcams.
Bonus code here:
// The TVideoSample class provides access to WebCams and similar Video-capture
// devices via DirectShow.
...
// Fills "FormatArr" with list of all supported video formats (resolution, compression etc...)
FUNCTION TVideoSample.LoadListOfResolution: HResult;
...
I am using Delphi XE3. I am trying to learn more about how to properly use and then free the pointers. I have two pointers lpParams and pHolder. I would like to know if I must used a typed pointer for pHolder and what the proper way to free both pinters in this routine. Thank you.
function TmyLine.LineOpen(dwLineDevice: Integer;
var wMediaMode: Integer;
phLine,
plTAPIVersion: pLongInt): Integer;
var lOwnership,
lResult: LongInt;
lpParams: pTLineCallparams; // first pointer
pHolder: Pointer; // second pointer
dwAddressID, dwMediaMode: DWORD;
begin
if not bDeviceInitialized then
lResult := -1
else
begin
pHolder := nil; // default is nil unless we are using the singleaddress method
lOwnership := 0;
if FPrivilege.bSingleAddress then
begin
lOwnership := lOwnership + LINEOPENOPTION_SINGLEADDRESS;
try
lpParams := AllocMem(SizeOf(TLineCallParams) + 128); // make up a big enough value
except
lpParams := nil;
exit; // should I exit here? what about if there is an error? do I free pHolder?
end;
lpParams.dwTotalSize := sizeof(TLineCallParams) + 128;
lpParams.dwAddressMode := LINEADDRESSMODE_ADDRESSID;
pHolder := lpParams; // is this ok? Do i need a typed pointer?
end;
lResult := lineOpen(Fline.hLineApp, // fnd 64bit, see if longint needs to be dword_ptr
dwLineDevice,
phLine,
plTAPIVersion^,
FTAPI.lExtVersion,
DWORD_PTR(Self), //Pointer to the control, it is passed to the Callback routine as the lCallBackInstance parameter
lOwnership,
wMediaMode,
pHolder); // using the pointer here
if lResult <> 0 then
begin
pHolder := nil;
lpParams := nil; // is the the right way to do this?
end
else
begin
lineSetStatusMessages(temp);
end;
end;
pHolder := nil; // is this the correct way to free things up?
if lpParams <> nil then freemem(lpParams, sizeof(TLineCallParams) + 128);
LineOpen := lResult;
end;
First, you are filling in the dwAddressMode field, but you are not filling in the dwAddressID field. You need to fill in the dwAddressID or else there is no point in using LINEOPENOPTION_SINGLEADDRESS.
Second, lineOpen() does not take ownership of the LINECALLPARAMS pointer that you pass to it. You must deallocate the memory yourself after lineOpen() exits, regardless of its result, eg:
function TmyLine.LineOpen(dwLineDevice: Integer;
var wMediaMode: Integer;
phLine,
plTAPIVersion: pLongInt): Integer;
var
lOwnership: DWORD;
lpParams: pTLineCallparams;
begin
Result := -1;
if not bDeviceInitialized then Exit;
lOwnership := 0;
lpParams := nil;
try
if FPrivilege.bSingleAddress then
begin
try
lpParams := AllocMem(SizeOf(TLineCallParams));
except
Exit;
end;
lpParams.dwTotalSize := sizeof(TLineCallParams);
lpParams.dwAddressID := ...; // don't forget to fill this in!
lpParams.dwAddressMode := LINEADDRESSMODE_ADDRESSID;
lOwnership := LINEOPENOPTION_SINGLEADDRESS;
end;
Result := lineOpen(Fline.hLineApp,
dwLineDevice,
phLine,
plTAPIVersion^,
FTAPI.lExtVersion,
DWORD_PTR(Self),
lOwnership,
wMediaMode,
lpParams);
finally
if lpParams <> nil then
FreeMem(lpParams, sizeof(TLineCallParams));
end;
if Result = 0 then
lineSetStatusMessages(...);
end;
Third, since you are only filling in standard fields of LINECALLPARAMS and not any extensions, you don't really need to dynamically allocate the LINECALLPARAMS on the heap at all. You can alternatively allocate it statically on the stack instead (which is why lineOpen() cannot take ownership of it), eg:
function TmyLine.LineOpen(dwLineDevice: Integer;
var wMediaMode: Integer;
phLine,
plTAPIVersion: pLongInt): Integer;
var
lOwnership: DWORD;
Params: TLineCallparams;
lpParams: pTLineCallparams;
begin
Result := -1;
if not bDeviceInitialized then Exit;
lOwnership := 0;
lpParams := nil;
if FPrivilege.bSingleAddress then
begin
Params.dwTotalSize := sizeof(TLineCallParams);
Params.dwAddressID := ...; // don't forget to fill this in!
Params.dwAddressMode := LINEADDRESSMODE_ADDRESSID;
lpParams := #Params;
lOwnership := LINEOPENOPTION_SINGLEADDRESS;
end;
Result := lineOpen(Fline.hLineApp,
dwLineDevice,
phLine,
plTAPIVersion^,
FTAPI.lExtVersion,
DWORD_PTR(Self),
lOwnership,
wMediaMode,
lpParams);
if Result = 0 then
lineSetStatusMessages(...);
end;
In Windows XP, with Delphi, how to get the master volume?
I know I can set up and down sending key strokes with keybd_event(VK_VOLUME_UP, 1, 0, 0); and keybd_event(VK_VOLUME_DOWN, 1, 0, 0);, but I don't know how to get the actual value of the volume.
The below is a little modification on the example code found here (credited there is Thomas Stutz). The example there sets the microphone volume. I just modified the component type - speaker destination instead of microphone source, and replaced mixerSetControlDetails with mixerGetControlDetails, and turned the setter into a getter of course. On the few systems I tested here (XPSp3, XPSp2, W2K, 98), it seems to work. The return of the function is the speaker out of the first (default) mixer - a value of 0-65535, the 'ShowMessage' in the button handler changes it into a percentage. But don't ask me more details about it, I really have no experience with the mixer api. Instead refer here f.i., though old the article really seemed to be comprehensive to me.
function GetSpeakerVolume(var bValue: Word): Boolean;
var {0..65535}
hMix: HMIXER;
mxlc: MIXERLINECONTROLS;
mxcd: TMIXERCONTROLDETAILS;
vol: TMIXERCONTROLDETAILS_UNSIGNED;
mxc: MIXERCONTROL;
mxl: TMixerLine;
intRet: Integer;
nMixerDevs: Integer;
begin
Result := False;
// Check if Mixer is available
nMixerDevs := mixerGetNumDevs();
if (nMixerDevs < 1) then
Exit;
// open the mixer
intRet := mixerOpen(#hMix, 0, 0, 0, 0);
if intRet = MMSYSERR_NOERROR then
begin
mxl.dwComponentType := MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
mxl.cbStruct := SizeOf(mxl);
// get line info
intRet := mixerGetLineInfo(hMix, #mxl, MIXER_GETLINEINFOF_COMPONENTTYPE);
if intRet = MMSYSERR_NOERROR then
begin
ZeroMemory(#mxlc, SizeOf(mxlc));
mxlc.cbStruct := SizeOf(mxlc);
mxlc.dwLineID := mxl.dwLineID;
mxlc.dwControlType := MIXERCONTROL_CONTROLTYPE_VOLUME;
mxlc.cControls := 1;
mxlc.cbmxctrl := SizeOf(mxc);
mxlc.pamxctrl := #mxc;
intRet := mixerGetLineControls(hMix, #mxlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
if intRet = MMSYSERR_NOERROR then
begin
ZeroMemory(#mxcd, SizeOf(mxcd));
mxcd.dwControlID := mxc.dwControlID;
mxcd.cbStruct := SizeOf(mxcd);
mxcd.cMultipleItems := 0;
mxcd.cbDetails := SizeOf(vol);
mxcd.paDetails := #vol;
mxcd.cChannels := 1;
intRet := mixerGetControlDetails(hMix, #mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
if intRet <> MMSYSERR_NOERROR then
ShowMessage('GetControlDetails Error')
else begin
bValue := vol.dwValue;
Result := True;
end;
end
else
ShowMessage('GetLineInfo Error');
end;
intRet := mixerClose(hMix);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Vol: Word;
begin
if GetSpeakerVolume(Vol) then
ShowMessage(IntToStr(Round(Vol * 100 / 65535)));
end;