AUDIO VIDEO
Menu |
|
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { if(OpenDialog1->Execute()){ MediaPlayer1->FileName = OpenDialog1->FileName; MediaPlayer1->Open(); } } //-------------------------------- |
Kompilujemy i uruchamiamy program, teraz po kliknięciu przycisku zostanie wywołane okno dialogowe umożliwiające wybranie pliku. Do tego celu właśnie służył kod 'OpenDialog1->Execute()', natomiast kod: 'MediaPlayer1->FileName = OpenDialog1->FileName;' przypisuje (wybraną w oknie dialogowym) nazwę pliku do właściwości 'FileName' komponentu 'MediaPlayer1'. Kod: 'MediaPlayer1->Open()' nakazuje komponentowi 'MediaPlayer1' otwarcie wskazanego pliku, ale nie spowoduje to jeszcze odtworzenia pliku, żeby odtworzyć plik należy kliknąć na przycisku play (zielony trójkąt). Gdyśmy chcieli, żeby plik został odtworzony zaraz po wybraniu go w oknie dialogowym, trzeba dodać kod 'MediaPlayer1->Play()'.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { if(OpenDialog1->Execute()){ MediaPlayer1->FileName = OpenDialog1->FileName; MediaPlayer1->Open(); MediaPlayer1->Play(); } } //-------------------------------- |
Można zrobić znacznie więcej np. zamiast przycisków dostęnych w 'MediaPlayer' utworzyć własne przyciski i uczynić komponent 'MediaPlayer1' niewidocznym zmieniając jego właściwość 'Visible' na 'true'. Niżej podaję przykłady obsługi plików dźwiękowych za pomocą własnych przycisków.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { MediaPlayer1->Play() // Uruchamia odtwarzanie pliku } //-------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { MediaPlayer1->Stop() // Zatrzymuje odtwarzanie pliku MediaPlayer1->Rewind(); // Przewija plik do początku } //-------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { MediaPlayer1->Close() // Wyłącza MediaPlayer, ponowne uruchomienie następuje dopiero po wywołaniu metody: MediaPlayer1->Open(). } //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { MediaPlayer1->Pause() // Włącza pauzę, po ponownym kliknięciu rozpocznie się odtwarzanie. } //-------------------------------- |
Regulacja głośności plików WAV.
W celu regulacji głośności plików wav można się posłużyć funkcją API 'waveOutSetVolume(void *hwo, unsigned long dwVolume)'. W tym celu w pliku nagłówkowym (np. Unit1.h) w sekcji private umieszczamy definicję funkcji int SetVoume(int x);.
Jeżeli nie używamy obiektu TMediaPlayer należy do projektu włączyć bibliotekę
#include <mmsystem.h>:
// Plik nagłówkowy np. Unit1.h. //-------------------------------- private: int SetVolume(int x); //-------------------------------- |
Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i importujemy do sekcji include plik:
// Plik źródłowy np. Unit1.cpp //-------------------------------- #include #pragma hdrstop #include "Unit1.h" #include //-------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //-------------------------------- int TForm1::SetVolume(int x) { int vol = (x * 65536) + x; return vol; } //-------------------------------- |
W zasadzie tyle już wystarczy, żeby regulować głośność, lecz potrzebny jest jeszcze komponent który będzie to zadanie realizował. Posłużymy się tutej komponentem 'TrackBar', który znajduje się na palecie komponentów Win32. Po umieszczeniu komponentu na formularzu zmieniamy przechodzimy do jego właściwości Max i jako wartość wpisujemy wartość 65535 i w zdarzeniu 'TrackBar1' - 'OnChange' wywołujemy utworzoną wcześniej funkcję 'SetVolume':
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::TrackBar1Change(TObject *Sender) { waveOutSetVolume(0, SetVolume(TrackBar1->Position)); } //-------------------------------- |
Całe zadanie można jednak zrealizować w znacznie prostszy sposób, a mianowicie zamiast tworzyć funkcję 'SetVolume' można wywołać funkcję API 'waveOutSetVolume' bezpośrednio w zdarzeniu 'TrackBar1' - 'OnChange':
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::TrackBar1Change(TObject *Sender) { waveOutSetVolume(0, (TrackBar1->Position * 65536) + TrackBar1->Position); } //-------------------------------- |
Umieszczanie plików wav w zasobach programu.
POPRAWIONO BŁĘDY
Umieszczanie plików *.wav wewnątrz aplikacji i odtwarzanie ich stamtąd jest w zasadzie bardzo proste i podobne do sposobu opisanego w poradzie umieszczanie bitmapy w zasobach programu. Tak więc otwieramy notatnik i umieszczamy w nim następujący wpis:
ID_SONG1 WAVE "nazwa_pliku.wav" |
Jak widać wpis składa się z trzech części:
W zasobach można umieścić więcej niż jeden plik dźwiękowy. Tak utworzony plik zapisujemy pod dowolną nazwą z rozszerzeniem *.rc, np. MySongs.rc. Teraz można postąpić z tak utworzonym plikiem zasobów na dwa różne sposoby. Po pierwsze można go skompilować do formatu *.res. W tym celu należy uruchomić konsolę (DOS) i w lini komend wpisać następujące polecenie:
brcc32 nazwa_pliku.rc nazwa_pliku.res
W katalogu z plikiem *.rc zostanie utworzony plik *.res. Tak utworzony plik (*.res) należy dołączyć do programu poprzez menu Project | Add to Project...
I tyle wystarczy, plik wav został już dołączony do programu.
Po drugie zamiast kompilować plik *.rc można go od razu dołączyć do programu poprzez menu Project | Add to Project..., plik zostanie automatycznie skompilowany do formatu *.res i dołączony podczas kompilacji samego programu.
Skoro zasoby zostały już dołączone do programu teraz należy je jakoś stamtąd wydobyć, w tym celu należy dołączyć do pliku źródłowego w sekcji include plik mmsystem.h:
// Plik źródłowy np. Unit1.cpp //-------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <mmsystem.h> //-------------------------------- |
Samo wydobycie dźwięku z zasobów jest banalnie proste i ogranicza się do wywołania funkcji PlaySound:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { PlaySound("ID_SONG1", HInstance, SND_ASYNC | SND_RESOURCE); } //-------------------------------- |
Jak widać nie podaje się nazwy pliku *.wav, który ma być odtwarzany, a tylko jego identyfikator w zasobach programu. Jeżeli chcemy, żeby dźwięk był odtwarzany non-stop wystarczy dołączyć jeden argument SND_LOOP:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { PlaySound("ID_SONG1", HInstance, SND_ASYNC | SND_LOOP |SND_RESOURCE); } //-------------------------------- |
DivX kontra TMediaPlayer.
Zdecydowałem się na umieszczenie tej mało przydatnej porady, ponieważ otrzymałem
kilka sygnałów, że komponent TMediaPlayer nie odtwarza plików AVI zakodowanych
kodekiem DivX, otóż nie do końca jest to prawdą. Problemy z DivX mogą być
spowodowane przez sam kodek, jednak próby przeprowadzone z kodekiem DivX w
wersji 5.2 dały pozytywne rezultaty i TMediaPlayer bez problemu odtwarzał pliki
AVI. Kolejny problem może być spowodowany przez obiekt TMediaPlayer, otóż ten
komponent ze środowiska Borland C++ Builder wersja 4, faktycznie stwarzał
problemy z odtwarzaniem DivX, ale nie zawsze, tylko wtedy gdy do otwarcia pliku
video używałem komponentu TOpenDialog, wtedy faktycznie występował błąd i plik
nie dawał się odtworzyć za pomocą obiektu TMediaPlayer, jednak bezpośrednie
wpisanie nazwy pliku do właściwości FileName obiektu TMediaPlayer, rozwiązywało
ten problem. Problem z DivX nie występuje w przypadku użycia komponentu
TMediaPlayer ze środowiska Borland C++ Builder wersja 6.
Przechwytywanie klatek video za pomocą TMediaPlayer.
W tej poradzie pokaże sposób na przechwycenie klatki video odtwarzanej w
obiekcie TMediaPlayer i wyświetlenie jej w obiekcie Image1, przedstawiony
tutaj kod będzie zawierał podstawowe informacje niezbędne do przechwycenia
pojedynczej klatki, ale można oczywiście dołączyć do programu obiekt TTimer
i przechwytywać całą sekwencję klatek. Zanim pokaże jak przechwytywać klatkę
video, przedstawię sposób na otwarcie pliku video i wyświetlenie go w obiekcie
TMediaPlayer na komponencie TPanel, ponieważ niektórzy mogą nie
wiedzieć jak to się robi. W tym celu proszę umieścić na formularzu obiekty
Panel1, Button1 do Button4, Image1 oraz obiekt
TMediaPlayer, którego właściwość Display ustawiamy na Panel1,
AutoOpen i Visible na false, rozmiar obiektu Panel1
ustawiamy na Width - 384, Height - 288, oczywiście rozmiar Panelu
może być różny. Wątku z otwarciem pliku nie będę opisywał, podam tylko kod,
reszty łatwo się domyśleć:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { if(OpenDialog1->Execute()) MediaPlayer1->FileName = OpenDialog1->FileName; } //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { if(FileExists(OpenDialog1->FileName)) { Screen->Cursor = crHourGlass; MediaPlayer1->Open(); // otwarcie pliku do odtwarzania. MediaPlayer1->DisplayRect = Rect(0, 0, 384, 288); // ustawienie rozmiaru okna video Panel1->BoundsRect = MediaPlayer1->DisplayRect; // dostosowanie rozmiaru Panel1 do rozmiaru okna video. Panel1->Left = 390; // określenie pozycji Panel1 na formularzu Panel1->Top = 8; // określenie pozycji Panel1 na formularzu MediaPlayer1->Play(); // uruchomienie odtwarzania Screen->Cursor = crDefault; } } //-------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { MediaPlayer1->Stop(); // zatrzymanie odtwarzania } //-------------------------------- |
Teraz dochodzimy do istoty tej porady, czyli do przechwytywania klatki video, w tym celu przechodzimy do pliku nagłówkowego i w sekcji private umieszczamy deklarację klasy TControlCanvas, a następnie w pliku źródłowym w konstruktorze klasy TForm1 umieszczamy definicję tej klasy i przypisujemy ją do obiektu Panel1, chodzi o to, że do przechwycenia klatki video z obiektu MediaPlayer1, który wyświetla obraz na obiekcie Panel1, niezbędna jest klasa TCanvas, ale obiekt Panel1 nie posiada tej klasy, więc trzeba mu ją przypisać za pośrednictwem klasy TControlCanvas:
// Plik nagłówkowy np. Unit1.h. //-------------------------------- private: TControlCanvas *FCanvas; //-------------------------------- |
// Plik źródłowy np. Unit1.cpp //-------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { FCanvas = new TControlCanvas; FCanvas->Control = Panel1; // to co niżej służy tylko do wypełnienia obiektu Image1 czarnym kolorem i może być pominięte. Image1->Canvas->Brush->Color = clBlack; Image1->Canvas->FillRect(Rect(0, 0, Image1->Width, Image1->Height)); } //-------------------------------- |
A teraz kod przechwytujący klatkę video i przepisujący ją do obiektu
Image1:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { Image1->Canvas->CopyRect(Rect(0, 0, Image1->Width, Image1->Height), FCanvas, Rect(0, 0, Panel1->Width, Panel1->Height)); } //-------------------------------- |
Obiekt Image1 powinien mieć te same wymiary co Panel1, chociaż nie jest to konieczne. Jeżeli będziecie chcieli przechwytywać sekwencję klatek video i zapisywać je do plików, to znacznie lepszym rozwiązaniem jest utworzenie dynamicznego obiektu TBitmap, a jeszcze lepiej jest zastosować podwójne buforowanie, żeby zapisywanie nie opóźniało przechwytywania:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { MediaPlayer1->Pause(); Screen->Cursor = crHourGlass; Graphics::TBitmap *Buf1 = new Graphics::TBitmap; Graphics::TBitmap *Buf2 = new Graphics::TBitmap; (Buf1 = Buf2)->Width = Panel1->Width; (Buf1 = Buf2)->Height = Panel1->Height; MediaPlayer1->Play(); for(int i = 0; i <= 24; i++) { (Buf2 = Buf1)->Canvas->CopyRect(Rect(0, 0, Buf1->Width, Buf1->Height), FCanvas, Rect(0, 0, Panel1->Width, Panel1->Height)); Buf2->SaveToFile("c:\\tempcap\\Cap" + IntToStr(i) + ".bmp"); } Screen->Cursor = crDefault; } //-------------------------------- |
No i na zakończenie, w czasie przechwytywania klatek nic nie może przysłaniać
obiektu Panel1, w przeciwnym razie, zostanie przychwycone dokładnie to co
go przysłania, dlatego lepiej jest wcześniej ustawić właściwość formularza
FormStyle na fsStayOnTop, żeby pozostawał zawsze na wierzchu. Nie
polecam zmiany stanu formularza tuż przed rozpoczęciem przechwytywania, ponieważ
w takim momencie formularz jest odświeżany i powstanie błąd podczas
przechwytywania.
Nagrywanie dźwięku i zapis do pliku *.wav
Nagrywanie dźwięku
np. za pomocą mikrofonu lub poprzez wejście liniowe można zrealizować za pomocą
funkcji mciSendCommand, którą znajduje się w bibliotece mmsystem.h.
Przy nagrywaniu dźwięku nie będziemy korzystać z komponentu TMediaPlayer.
Zanim zaczniemy pisać kod programu, należy umieścić na formularzu dwa przyciski
typu TButton i komponent StatusBar1 w którym tworzymy jeden panel. Na obiekcie
StatusBar1 będzie wyświetlana informacja o rozpoczęciu i zakończeniu nagrywania,
w przycisku Button1 rozpoczniemy nagrywanie, a w Button2 zakończymy nagrywanie i
zapiszemy do pliku *.wav.
Najpierw deklarujemy w pliku nagłówkowy w sekcji private kilka zmiennych
oraz cztery funkcje. Deklarujemy dwie funkcje o takiej samej nazwie
StartRecording, nie ma tutaj pomyłki obydwie funkcje mają identyczne nazwy,
ale identyczne nie są, pierwsza funkcja StartRecording nie pobiera
żadnych argumentów i będzie ona służyła do rozpoczęcia nagrywania, druga funkcja
StartRecording będzie pobierała dwa argumenty pierwszy to limit czasu
nagrania, a drugi to nazwa pliku do którego zostanie zapisane nagranie.
Wykorzystanie dwóch funkcji o tej samej nazwie, ale różniących się pobieranymi
argumentami nazywa się przeciążeniem funkcji. Mamy dwie funkcje o tej samej
nazwie, a to która zostanie wywołana będzie zależało od tego jakie argumenty
przekażemy funkcji. Jeżeli wywołamy pierwszą funkcję StartRecording to
zostanie uruchomione nagrywanie bez limitu czasu nagrywania, a przerwanie
nagrywania nastąpi po wywołaniu funkcji StopRecording, którą również
tutaj zadeklarujemy. Jeżeli wywołamy drugą funkcję, czyli przekażemy do funkcji
StartRecording dwa argumenty, pierwszy limit czasu nagrania i drugi nazwa
pliku z nagraniem, to nagranie i zapis do pliku odbędzie się automatycznie,
czyli po zadanym czasie nagrywanie zostanie przerwane i zapisane do pliku, w tym
przypadku nie zachodzi konieczność wywoływania funkcji StopRecording,
gdyż przerwanie nagrywania nastąpi automatycznie po upływie zadanego czasu.
Zaznaczę tutaj od razu, że odliczaniem czasu nagrania steruje funkcja
mciSendCommand, wiec nie korzystamy z obiektu typu TTimer,
lub żadnego podobnego. Czwarta funkcja RecordError posłuży tylko do
wyświetlania ewentualnych komunikatów o błędach.
// Plik nagłówkowy np. Unit1.h //-------------------------------- private: MCI_OPEN_PARMS mciOpen; MCI_RECORD_PARMS mciRecord; MCI_SAVE_PARMS mciSave; // jeżeli mamy na formularzu komponent typu TMediaPlayer to deklarujemy: Mmsystem::MCI_SAVE_PARMS mciSave; DWORD flags; long DeviceID; void StartRecording(long time, char *FileName); void StartRecording(void); void StopRecording(char *FileName); bool RecordError(long error, char *caller); |
Teraz w pliku źródłowym definiujemy funkcje. Trzeba również pamiętać o włączeniu do sekcji include biblioteki mmsystem.h:
// Plik źródłowy np. Unit1.cpp #include <mmsystem.h> //--------------------------------------------------------------------------- void TForm1::StopRecording(char *FileName) { StatusBar1->Panels->Items[0]->Text = "Zakończono nagrywanie..."; mciSave.lpfilename = FileName; flags = MCI_SAVE_FILE|MCI_WAIT; RecordError(mciSendCommand(DeviceID, MCI_SAVE, flags, DWORD(&mciSave)), "SaveToFile"); RecordError(mciSendCommand(DeviceID, MCI_CLOSE, flags, DWORD(NULL)), "Close"); } //--------------------------------------------------------------------------- void TForm1::StartRecording(long time, char *FileName) { StatusBar1->Panels->Items[0]->Text = "Rozpoczęto nagrywanie..."; Application->ProcessMessages(); mciOpen.lpstrDeviceType = "waveaudio"; mciOpen.lpstrElementName = ""; flags = MCI_OPEN_ELEMENT|MCI_OPEN_TYPE; RecordError(mciSendCommand(0, MCI_OPEN, flags, DWORD(&mciOpen)), "OpenWave"); DeviceID = mciOpen.wDeviceID; mciRecord.dwTo = time; flags = MCI_TO|MCI_WAIT; RecordError(mciSendCommand(DeviceID, MCI_RECORD, flags, DWORD(&mciRecord)), "RecordWave"); StatusBar1->Panels->Items[0]->Text = "Zakończono nagrywanie..."; mciSave.lpfilename = FileName; flags = MCI_SAVE_FILE|MCI_WAIT; RecordError(mciSendCommand(DeviceID, MCI_SAVE, flags, DWORD(&mciSave)), "SaveToFile"); RecordError(mciSendCommand(DeviceID, MCI_CLOSE, flags, DWORD(NULL)), "Close"); } //--------------------------------------------------------------------------- void TForm1::StartRecording(void) { StatusBar1->Panels->Items[0]->Text = "Rozpoczęto nagrywanie..."; Application->ProcessMessages(); mciOpen.lpstrDeviceType = "waveaudio"; mciOpen.lpstrElementName = ""; flags = MCI_OPEN_ELEMENT|MCI_OPEN_TYPE; RecordError(mciSendCommand(0, MCI_OPEN, flags, DWORD(&mciOpen)), "OpenWave"); DeviceID = mciOpen.wDeviceID; RecordError(mciSendCommand(DeviceID, MCI_RECORD, flags, DWORD(&mciRecord)), "RecordWave"); } //--------------------------------------------------------------------------- bool TForm1::RecordError(long error, char *caller) { char msg[100]; if(error != 0) { mciGetErrorString(error, msg, 100); Application->MessageBox(msg, caller, MB_OK | MB_ICONEXCLAMATION); return true; } return false; } //--------------------------------------------------------------------------- |
Najpierw przykład wywołania funkcja StartRecording z limitem czasu nagrywania ustawionym na 30 sekund:
// Plik nagłówkowy np. Unit1.h //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { StartRecording(30000, "c:\\test.wav"); // 1000 = 1 sekunda } //--------------------------------------------------------------------------- |
Na zakończenie przykład wywołania funkcji StartRecording bez limitu czasu nagrywania, teraz w celu przerwania nagrywania trzeba wywołać funkcję StopRecording:
// Plik nagłówkowy np. Unit1.h //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { StartRecording(); // rozpoczęcie nagrywania } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { StopRecording("c:\\test.wav"); // zakończenie nagrywania i zapis do pliku } //--------------------------------------------------------------------------- |
Mikser - regulacja sterowników dźwięku.
Regulacja sterowników głośności (mixer) umożliwia dostosowanie ustawień dla dźwięków odtwarzanych przez komputer takich jak Wave/MP3, MIDI, mikrofon oraz aplikacji multimedialnych, takich jak Odtwarzacz CD, Odtwarzacz multimedialny lub Rejestrator dźwięku:
Do regulacji sterowników głośności służy zestaw funkcji dostępnych w bibliotece mmsystem.h, dlatego trzeba włączyć tą bibliotekę do projektu w sekcji include pliku źródłowego lub nagłówkowego.
Regulacja głośności wymaga skorzystania z następujących funkcji:
MMRESULT mixerOpen(LPHMIXER phmx, UINT uMxId, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); - funkcja otwierająca urządzenie miksujące, nie powoduje to oczywiście wywołania apletetu sterowania regulacji dźwięku, a tylko udostępnia urządzenie miksujące.
MMRESULT mixerGetLineInfo(HMIXEROBJ hmxobj, LPMIXERLINE pmxl, DWORD fdwInfo); - funkcja wyszukująca informacji o określonym sterowniku, wymagana jest tutaj struktura MIXERLINE, która pozwala określić regulowany sterownik.
MMRESULT mixerGetLineControls(HMIXEROBJ hmxobj, LPMIXERLINECONTROLS pmxlc, DWORD fdwControls); - funkcja wyszukująca układy sterowania i przejmująca nad nimi kontrolę, wymagana jest tutaj struktura MIXERLINECONTROLS, która zawiera informacje o układach sterowania.
MMRESULT mixerSetControlDetails(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd,DWORD fdwDetails); - funkcja ustawiająca właściwości określonego sterownika, wymagana jest tutaj struktura MIXERCONTROLDETAILS, służąca do kontroli (regulacji) określonego sterownika
Po więcej informacji odsyłam do plików pomocy środowiska C++ Builder.
Funkcję regulującą siłę dźwięku dla poszczególnych sterowników nazwałem SetVolume, pobiera ona dwa argumenty, pierwszy to typ sterownika, drugi to siła dźwięku, wartość ta musi zawierać się w przedziale od 0 do 65535, wartości powyżej są traktowane jak wartość maksymalna
//--------------------------------------------------------------------------- BOOL __fastcall SetVolume(DWORD compType, DWORD cVolume) { MMRESULT result; HMIXER hMixer; result = mixerOpen(&hMixer, MIXER_OBJECTF_MIXER, 0, 0, 0); MIXERLINE ml = {0}; ml.cbStruct = sizeof(MIXERLINE); ml.dwComponentType = compType; // typ sterownika, któremu regulujemy dźwięk result = mixerGetLineInfo((HMIXEROBJ) hMixer, &ml, MIXER_GETLINEINFOF_COMPONENTTYPE); MIXERLINECONTROLS mlc = {0}; MIXERCONTROL mc = {0}; mc.cbStruct = sizeof(MIXERCONTROL); mlc.cbStruct = sizeof(MIXERLINECONTROLS); mlc.dwLineID = ml.dwLineID; mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; mlc.cControls = 1; mlc.pamxctrl = &mc; mlc.cbmxctrl = sizeof(MIXERCONTROL); result = mixerGetLineControls((HMIXEROBJ) hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE); MIXERCONTROLDETAILS mcd = {0}; MIXERCONTROLDETAILS_UNSIGNED mcdu = {0}; mcdu.dwValue = cVolume; // siła dźwięku zawiera się pomiędzy 0 i 65535 mcd.cbStruct = sizeof(MIXERCONTROLDETAILS); mcd.hwndOwner = 0; mcd.dwControlID = mc.dwControlID; mcd.paDetails = &mcdu; mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); mcd.cChannels = 1; result = mixerSetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_SETCONTROLDETAILSF_VALUE); return result; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { SetVolume(MIXERCONTROL_CONTROLTYPE_CUSTOM, 30000); // głośność główna } //--------------------------------------------------------------------------- |
Niżej przedstawiam spis typu sterowników, tyle znalazłem, ale to chyba nie wyczerpuje wszystkich możliwości:
Odtwarzanie:
MIXERLINE_COMPONENTTYPE_DST_SPEAKERS
- Play Control - głośność główna
MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_DST_DIGITAL
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_DST_HEADPHONES
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_DST_LINE
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_DST_MONITOR
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_DST_TELEPHONE
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_DST_UNDEFINED
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_DST_VOICEIN
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_SRC_DIGITAL
- Wave/Mp3
MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER
- MIDI Synthe
MIXERLINE_COMPONENTTYPE_SRC_ANALOG
- PC Speaker
MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC
- CD Audio
MIXERLINE_COMPONENTTYPE_SRC_LINE
- Line-In
MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
- Microphone
MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE
- TAD-In
Nagrywanie:
MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED
- Wave/Mp3
Te definicje odnoszą się do konkretnych sterowników karty
dźwiękowej i mogą być różne w odniesieniu do różnych kart, opisy opracowałem w
oparciu o własną kartę dźwiękową, poza tym nie wiem jak to się ma do kart HD.
W poradzie Mixer - odczytywanie poziomu ustawień sterowników dźwięku pokaże jak odczytać ustawienia głośności poszczególnych sterowników, oraz jak sterować poziomem dźwięku za pomocą komponentu TrackBar i jednocześnie odczytywać poziom dźwięku w tym samym komponencie.
Mikser - odczytywanie poziomu ustawień sterowników dźwięku.
Większość funkcji niezbędnych do odczytania poziomu ustawień głośności została zaprezentowana w poprzedniej poradzie, dlatego tutaj przedstawię tylko funkcję:
MMRESULT mixerGetControlDetails(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails); - funkcja odczytująca ustawienia poziomu dźwięku wybranego sterownika, wymagana jest tutaj struktura MIXERCONTROLDETAILS, służąca do kontroli (regulacji) określonego sterownika.
Funkcję regulującą siłę dźwięku dla poszczególnych sterowników
nazwałem GetVolume, pobiera ona jeden argumenty, jest to
typ sterownika dla którego dźwięk jest sprawdzany. Funkcja zwraca ustawienia
poziomu dźwięku dla wybranego sterownika.
W przykładzie pokaże jak w zdarzeniu OnClick dla przycisku Button1 sprawdzić
poziom dźwięku głównego sterownika dźwięku:
#include "mmsystem.h" //--------------------------------------------------------------------------- DWORD GetVolume(DWORD cType) { MMRESULT result; HMIXER hMixer; result = mixerOpen(&hMixer, MIXER_OBJECTF_MIXER, 0, 0, 0); MIXERLINE ml = {0}; ml.cbStruct = sizeof(MIXERLINE); ml.dwComponentType = cType; result = mixerGetLineInfo((HMIXEROBJ) hMixer, &ml, MIXER_GETLINEINFOF_COMPONENTTYPE); MIXERLINECONTROLS mlc = {0}; MIXERCONTROL mc = {0}; mlc.cbStruct = sizeof(MIXERLINECONTROLS); mlc.dwLineID = ml.dwLineID; mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; mlc.cControls = 1; mlc.pamxctrl = &mc; mlc.cbmxctrl = sizeof(MIXERCONTROL); result = mixerGetLineControls((HMIXEROBJ) hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE); MIXERCONTROLDETAILS mcd = {0}; MIXERCONTROLDETAILS_UNSIGNED mcdu = {0}; mcd.cbStruct = sizeof(MIXERCONTROLDETAILS); mcd.hwndOwner = 0; mcd.dwControlID = mc.dwControlID; mcd.paDetails = &mcdu; mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); mcd.cChannels = 2; mixerGetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_SETCONTROLDETAILSF_VALUE); return mcdu.dwValue; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { int Volume = GetVolume(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS); Label1->Caption = "Siła dźwięku: " + (String)Volume; } //--------------------------------------------------------------------------- |
Teraz pokaże jak za pomocą jednego komponentu TrackBar1 jednocześnie sprawdzać i regulować poziom dźwięku dla wybranego sterownika, w przykładzie będzie to główny sterownik dźwięku. Ten kod jest o tyle interesujący, że całość zadania będzie wywoływana w jednym zdarzeniu OnChange dla obiektu TrackBar1, czyli będzie tutaj regulowany poziom głośności z wykorzystaniem funkcji SetVolume zaprezentowanej w poprzedniej poradzie i jednocześnie będzie sprawdzany poziom głośności za pomocą funkcji GetVoulume, i ten sam TrackBar1 będzie miał ustawiany ten poziom głośności. Zachodzi tutaj oczywisty konflikt pomiędzy zmianą ustawienia wartości obiektu TrackBar1 i jednoczesnym jej ustawianiem, ci z was którzy próbowali kiedykolwiek stworzyć program do odtwarzania filmów z jednym paskiem do przewijania i jednocześnie pokazywania pozycji odtwarzanego filmu, wiedzą o czym mówię. Problem daje sie rozwiązać za pomocą prostego algorytmu. Żeby nasz program mógł sprawdzać poziom dźwięku zmieniany przez standardowe sterowniki lub mikser systemowy i na bieżąco go wyświetlać, trzeba zamontować hak systemowy do przechwytywania komunikatów wysyłanych przez system. Z takim rozwiązanie wiążą się dwa problemy, pierwszy to taki, że program antywirusowy może od razu zablokuje nam program i wyświetlić komunikat z ostrzeżeniem, że proces próbuje uzyskać dostęp do innych procesów, w takim przypadku należy dodać nasz program do listy zaufanych aplikacji. Drugi problem wiąże się z komputerami przenośnymi (nootebok, laptop), w takich komputerach często regulacja dźwięku odbywa się za pomocą pokrętła i to właśnie pokrętło może powodować zawieszenia aplikacji a nawet całego systemu, w takim przypadku zamiast przechwytywania komunikatów trzeba raczej skorzystać z obiektu TTimer. Na komputerach wyposażonych w klawiaturę z regulatorami dźwięku w postaci przycisków (nie pokręteł) wszystko działa prawidłowo, czyli regulacja dźwięku bezpośrednio z klawiatury jest wychwytywana i wyświetlana w programie.
Kilka uwag odnośnie konstrukcji kodu.
Na zakończenie jeszcze algorytm, który można umieścić w zdarzeniu OnChange obiektu TrackBar1 wyświetlający w obiekcie Label1 procentowy poziom wysterowania dźwięku:
//--------------------------------------------------------------------------- void __fastcall TForm1::TrackBar1Change(TObject *Sender) { int value, proc; String result; value = (TrackBar1->Position - 65535) * -1; proc = (value *10000)/65535; if(proc != 10000) { if(proc != 0) result = FormatCurr("Siła dźwięku: #%", (proc/100)); else result = "Siła dźwięku: 0%"; } else result = "Siła dźwięku: 100%"; Label1->Caption = result; SetVolume(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, value); } //--------------------------------------------------------------------------- |