AUDIO VIDEO

Menu

  1. Odtwarzanie plików dźwiękowych.

  2. Regulowanie głośności plików WAV.

  3. Umieszczanie Plików Wav w zasobach programu.

  4. DIvX kontra TMediaPlayer.

  5. Przechwytywanie klatek video za pomocą TMediaPlayer.

  6. Nagrywanie dźwięku i zapis do pliku *.wav.

  7. Mixer - regulacja sterowników dźwięku.

  8. MIXER - odczytywanie poziomu ustawień sterowników dźwięku.

 


Odtwarzanie plików dźwiękowych.

  W celu odtwarzania plików dżwiękowych najlepiej jest posłużyć się komponentem 'MediaPlayer', który znajduje się na palecie komponentów w zakładce 'System'. Po umieszczeniu komponentu na formularzu widać, że zawiera on przyciski niezbędne do obsługi plików dźwiękowych. Żeby odtworzyć plik dźwiękowy (*.wav, *.mp3, *.mid, *.midi, *.mpg, *.mpeg, *.avi, itp.) wystarczy w Inspektorze Obiektów (Object Inspector) zmienić właściwość 'FileName' wstawiając ścieżkę dostępu wraz z nazwą pliku, który chcemy odtworzyć. Trzeba również ustawić właściwość 'AutoEnable' na true, następnie należy skompilować i uruchomić program. Po uruchomieniu programu wystarczy kliknąć na przycisku play (zielony trójkąt) i plik zostanie odtworzony. Jednak ten sposób nie jest najlepszy ponieważ nazwa pliku, który chcemy odtworzyć musi być zdefiniowana przed skompilowaniem programu, dlatego najlepiej będzie posłużyć się dodatkowo komponentem 'OpenDialog' (zakładaka Dialogs). Umieszczamy na formularzu komponent 'MediaPlayer' (nie zmieniamy jego właściwości), następnie umieszczamy na formularzu komponent 'OpenDialog' i zmieniamy jego właściwość 'FilterName' - uruchomi się 'Filter Editor' - jako 'FilterName' wpisujemy np.: 'Pliki dźwiękowe (*.wav;*.mp3;*.midi)', a jako 'Filter' wpisujemy '*.wav;*.mp3;*.midi' - to sprawi, że komponent 'OpenDialog' będzie "widział" tylko pliki w formatach 'WAV, MP3, MIDI'. Nastęnie umieszczamy na formularzu komponent 'Button', klikamy dwukrotnie na komponencie 'Button', program wstawi do pliku źródłowego (np. Unit1.cpp) wywołanie zdarzenia OnClick. Umieszczamy obsługę zdarzenia według podanego przykładu.

// 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.
}
//--------------------------------


...powrót do menu. 

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: , a potem dodajemy obsługę funkcji zdefiniowanej w pliku nagłówkowym.

// 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);
}
//--------------------------------

...powrót do menu. 

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:

  1. ID_SONG1                   -    jest to identyfikator używany do zidentyfikowania pliku, który będzie odtwarzany. Nazwa identyfikatora jest dowolna jednak musi być jednoczęściowa i nie może zawierać żadnych znaków specjalnych, czyli np. ID SONG1 jest już źle ponieważ identyfikator składa się z dwóch części.
  2. WAVE                           -    to jest identyfikator rodzaju zasobu i w przypadku plików *.wav ten identyfikator musi być zawsze taki sam, czyli nie można go zmieniać.
  3. "nazwa_pliku.wav"     -    to - co się rozumie samo przez się - jest nazwa pliku, który ma być dołączony do zasobów. Jeżeli dołączany plik znajduje się w innym katalogu niż sam plik z zasobami to należy podać do niego pełną ścieżkę dostępu, np. "c:\Katalog\nazwa_pliku.wav".

    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);
}
//--------------------------------

...powrót do menu. 

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.

...powrót do menu. 

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.

...powrót do menu. 

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
}
//---------------------------------------------------------------------------


...powrót do menu. 

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:

 

mixer

 

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.

...powrót do menu. 

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);
}
//---------------------------------------------------------------------------

...powrót do menu.