Pour une DLL réalisée en Delphi V6 Personal Edition, je suis en train de développer des fonctions pour gérer la sortie haut-parleur du système. J'arrive à lire et positionner le volume général ainsi ue l'indicateur "muet", et le rendre b-directionnel via une procédure call-back, de sorte que la scrollbar du volume de mon application se positionne tout seul lorsqu'on change le volume général du mixeur, etc.

Par contre, j'ai un problème pour lire la valeur instantanée du volume, sur les deux canaux (channel peak value). Je trouve sans problème la valeur instantanée du volume général, mais le problème se pose en essayant de lire les valeurs instantanées individuelles des canaux.

Je suis arrivé à créer une unité qui peut lire correctement la valeur instantanée du volume sur le canal gauche, mais celle du canal droit reste fixe à zéro. Pourtant, le nombre de canaux que l'interface retourne, est bien de 2. Et en lançant le VuMeter de SoureForge, je vois bien les deux aiguilles bouger, et donc, les deux canaux travaillent bien.

Pire encore, je ne trouve la valeur du canal gauche uniquement que si, entre l'API récupérant la valeur et mon code pour la traiter, j'intercale un SendMessage pour réafficher le titre de la fenêtre principale (que j'ai capté et mémorisé à l'initialisation du programme). Si je ne fais pas ça, même la valeur pour le canal gauche reste à zéro ! Je suis tombé sur cette astuce bizarre lorsque, en désespoir de cause, j'avais intercalé un ShowMessage à cet endroit. Et comme par magie, ma valeur du canal gauche apparaissait. Et elle restait en remplaçant le ShowMessage par un SendMessage. Pourquoi ? Problème de timing ?

Je poste ci-après mon code complet de cette unité. Il comprend 2 fonctions, dont l'étrange format d'appel est imposé par le langage spécifique auquel est destiné cette DLL:
- une fonction InitSpeakerControl dont les 4 paramètres sont la valeur maximale attendue pour les nveaux de son, ainsi que 3 adresses de variables de type Integer, pour y déposer de façon automatique les niveaux de volume gauche, droite et général.
- une fonction CloseSpeakerControl sans paramètres qui est supposé faire le ménage et tout libérer (elle n'est pas complète, à l'évidence)

La fonction InitSpeakerControl lance un timer qui, à chaque itération, va chercher le volume général et celui des deux canaux, va transformer ces valeurs pour aboutir à une mise à l'échelle par rapport à l'intervalle imposé, puis va transmettre ces valeurs au programme appelant en les déposant dans les adresses des 3 variables. C'est un fonctionnement simpliste. Le problème se situe aux endroits marqués dans le code, par des commentaires.

Voici le source:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
unit KGF_unit_SpeakerControl;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Dialogs, ActiveX, ComObj, MMSystem,     
  ComCtrls, ExtCtrls,
  KGF_unit_data;          // uniquement pour la variable MainFormHandle qui y est définie de façon globale
 
type
  EDATAFLOW = TOleEnum;
  EROLE = TOleEnum;
 
  IMMDevice = interface(IUnknown)
    ['{D666063F-1587-4E43-81F1-B948E807363F}']
    function Activate(const iid: TGUID; const dwClsCtx: UINT; const pActivationParams: PPropVariant; out ppInterface: IUnknown)
      : HRESULT; stdcall;
  end;
 
  IMMDeviceCollection = interface(IUnknown)
    ['{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}']
  end;
 
  IMMDeviceEnumerator = interface(IUnknown)
    ['{A95664D2-9614-4F35-A746-DE8DB63617E6}']
    function EnumAudioEndpoints(const dataFlow: EDATAFLOW; const dwStateMask: DWORD; out ppDevices: IMMDeviceCollection): HRESULT; stdcall;
    function GetDefaultAudioEndpoint(const dataFlow: EDATAFLOW; const role: EROLE; out ppEndpoint: IMMDevice): HRESULT; stdcall;
  end;
 
  IAudioMeterInformation = interface(IUnknown)
    ['{C02216F6-8C67-4B5B-9D00-D008E73E0064}']
    function GetPeakValue(out pfPeak: Single): HRESULT; stdcall;
    function GetMeteringChannelCount(out pnChannelCount: UINT): HRESULT; stdcall;
    function GetChannelsPeakValues(u32ChannelCount: UINT; out afPeakValues: pSingle): HRESULT; stdcall;
    function QueryHardwareSupport(out pdwHardwareSupportMask: UINT): HRESULT; stdcall;
  end;
 
 
const
  IID_IMMDeviceEnumerator: TGUID = '{A95664D2-9614-4F35-A746-DE8DB63617E6}';
  CLASS_IMMDeviceEnumerator: TGUID = '{BCDE0395-E52F-467C-8E3D-C4579291692E}';
  IID_IAudioMeterInformation: TGUID = '{C02216F6-8C67-4B5B-9D00-D008E73E0064}';
  eRender = $00000000;
  eConsole = $00000000;
 
type TDummyForm = class
  class procedure Timer1Timer(Sender: TObject);
end;
 
var
  peak: IAudioMeterInformation = nil;
  SpeakerTimer: TTimer;
   pVolumeOutputL, pVolumeOutputR, pVolume: pinteger;   // <=== utilisé pour retourner dynamiquement les peak values deu volume
  MaxOutput: Integer;
  device: IMMDevice;
  deviceEnumerator: IMMDeviceEnumerator;
  nChannels: UINT;
  ApplicationTitle: String;                                                    // utilisé pour mémoriser le titre de la fenêtre principale
 
implementation
 
 
function InitSpeakerControl(MaxV: integer; poutL, poutR, pVol: pinteger):integer; stdcall; export;
var
    n: integer;
begin
  SpeakerTimer := TTimer.Create(nil);
  SpeakerTimer.Enabled := False;
  SpeakerTimer.OnTimer := TDummyForm.Timer1Timer;
  SpeakerTimer.Interval:= 200;
  MaxOutput := MaxV;
  pVolumeOutputL := poutL;
  pVolumeOutputR := poutR;
  pVolume := pVol;
  CoCreateInstance(CLASS_IMMDeviceEnumerator, nil, CLSCTX_ALL, IID_IMMDeviceEnumerator, deviceEnumerator);
  deviceEnumerator.GetDefaultAudioEndpoint(eRender, eConsole, device);
  device.Activate(IID_IAudioMeterInformation, CLSCTX_ALL, nil, IUnknown(peak));
  peak.GetMeteringChannelCount(nChannels);
  SpeakerTimer.Enabled := true;
 
  // les 3 lignes suivantes sont essentielles - le titre de la fenêtre principale doit être réaffiché  dans la routine OnTimer, sinon, les valeurs restent à zéro !
  n := SendMessage(MainFormHandle,WM_GETTEXTLENGTH,0,0) + 1;                          // retourner la longeur du titre de la fenêtre principale
  SetLength(ApplicationTitle,n);                                                                                 // réserver de l'espace pour le titre
  SendMessage(MainFormHandle,WM_GETTEXT,n,integer(@ApplicationTitle[1]));           // récupérer le titre de la fenêtre principale
 
  result := 0;
end;
exports InitSpeakerControl;
 
function CloseSpeakerControl():integer; stdcall; export;
begin
  if assigned(SpeakerTimer) then begin
    SpeakerTimer.Enabled := false;
    SpeakerTimer.Free;
//    peak.Free;
  end;
  result := 0;
end;
exports CloseSpeakerControl;
 
class procedure TDummyForm.Timer1Timer(Sender: TObject);
var
  ChannelVolumes: array[1..2] of Single;
  pVolumes: pSingle;
  Temp: Single;
  vL, vR, vV: integer;
  s: string;                                        // variable temporaire pour afficher le titre - utiliser ici la variable ApplicationTitle NE MARCHE PAS !
begin
  ttimer(sender).Enabled := false;                                // arrêter le timer, le temps de traiter l'évènement
  ChannelVolumes[1] := 0;
  ChannelVolumes[2] := 0;
  pVolumes := pSingle(@ChannelVolumes[1]);
 
  peak.GetPeakValue(Temp);
  peak.GetChannelsPeakValues(nChannels,pVolumes);      // <========== Seul [1] est retourné, [2] reste à zéro !
 
  // sans les deux lignes suivantes, le contenu du tableau ChannelVolumes reste à zéro, même pour le [1].
  // avec ces deux lignes stupides, le [1] est renseigné correctement,, mais le [2] reste toujours à zéro !
  s := ApplicationTitle;
  SendMessage(MainFormHandle,WM_SETTEXT,0,integer(@s[1]));
 
  // produire des valeurs entières entre 0 et 65535
  vL := Round(ChannelVolumes[1] * 65535);
  vR := Round(ChannelVolumes[2] * 65535);
 
  // traduire en valeurs entre 0 et 100
  vL := Round((vL * MaxOutput)/65536);
  vR := Round((vR * MaxOutput)/65536);
  vV := Round(temp * MaxOutput);
 
  // envoyer ces valeurs dans des variables de l'application, si leur adresse a été passée en paramètre à la fonction InitSpeakerControl
  if pVolumeOutputL<>nil then pVolumeOutputL^ := vL;
  if pVolumeOutputR<>nil then pVolumeOutputR^ := vR;
  if pVolume<>nil then pVolume^ := vV;
  ttimer(sender).Enabled := true;                          // relancer le timer
end;
 
end.
J'ai deux questions précises:

1. pourquoi, sans les 2 lignes
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
  s := ApplicationTitle;
  SendMessage(MainFormHandle,WM_SETTEXT,0,integer(@s[1]));
j'ai uniquement zéro comme volume, alors qu'avec ces lignes au moins le volume gauche apparaît et est correct ?

2. pourquoi, avec ou sans ces deux lignes, je ne peux pas avoir le volume du canal droit ?

Ca fait des jours que je me bats avec ça, et je n'arrive pas à comprendre. Je vous serais très reconnaissant pour un coup de main !