Кратко разберём упрощённую схему взаимодействия пользовательского приложения, ядра ОС и аудиоустройства.
1. Пользовательское приложение инициализирует аудиоустройство. Для этого приложение вызывает WinAPI функцию ядра ОС.
2. Ядро обращается к драйверу аудиоустройства с запросом на инициализицию. Если инициализация прошла успешно, значит устройство и его драйвер установлены и готовы к работе. Иначе ядро вернёт ошибку (отсутствие драйвера либо необходимого устройства).
3. Поступающую информацию от аудиоустройства необходимо хранить в месте, куда её сможет записать ядро и прочитать пользовательское приложение. Выделим для этого участок свободной (незанятой памяти) и передадим адрес участка в ядро. При инициализации устройства мы также указываем ядру способ обратной связи с приложением (чтобы ядро могло уведомить приложение о событиях).
4. Приложение вызывает WinAPI функцию старта аудиоустройства. Драйвер читает данные непосредственно с устройства, а ядро пишет их в выделенный участок в памяти. Когда участок заполнен данными, ядро связывается с приложением. В случае отправки сообщения передаётся указатель на буфер в памяти. Буфер, помечается как заполненный и может быть прочитан из приложения. Драйверу аудиоустройства можно выделить больше одного буфера. Тогда после заполнения текущего буфера начнёт заполняться следующий по порядку буфер.
5. Приложение получив сообщение от ядра о том что буфер заполнен, может читать из него информацию. После, как и в начале, передаёт указатель на обработанный буфер назад ядру, которое ставит этот буфер в очередь драйвера (карусель буферов).
Общая схема я надеюсь понятна. Теперь мы разберём как получить буфер сэмплов из аудиоустройства на практике. Существует несколько вариантов, наиболее распространённые – это с помощью библиотеки DirectX (DSound) и с помощью WinAPI.
Мы будем делать последним способом, язык выберем C++.
Объявим необходимые переменные.
//---------------------------------------------------------------------------
#include <mmsystem.h> //заголовочный файл содержащий прототипы мультимедийных ф-й
//---------------------------------------------------------------------------
#define DinBufSize 1024 //Размер буфера данных в динамическом режиме
#define FreqDis 8000 //Частота дискретизации отсчётов (в одну секунду)
#define Channels 1 //Количество каналов
#define BitPesSempl 8 //Разрядность под сэмпл
//---------------------------------------------------------------------------
WAVEFORMATEX WaveFormat; //Формат записи-чтения
WAVEHDR WaveHdrIn1,WaveHdrIn2,WaveHdrIn3,WaveHdrIn4; //Заголовки буферов
HWAVEIN hWaveIn; //Устройство записи
byte * buf1In; //Буферы для структуры WAVEHDR
byte * buf2In;
byte * buf3In;
byte * buf4In;
Поставим ф-ю первой по порядку в файле реализации (*.cpp)
//калбек вызываемая ф-я при заполнении очередного буфера в очереди устройства записи
void CALLBACK FullBuf(HWAVEIN hwi,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)
{
//если сообщение о заполнении буфера и отправлено устройством записи
if(uMsg==WIM_DATA && hwi==hWaveIn)
{
//указатель на полученный буфер
WAVEHDR * TempHdr= (WAVEHDR *)dwParam1;
//буфер сэмплов, можем производить с ним операции
TempHdr->lpData;
//возвращаем буфер в очередь устройства записи
waveInAddBuffer(hWaveIn,TempHdr,sizeof(WAVEHDR));
}
}
При старте приложения
void __fastcall TForm1::FormShow(TObject *Sender)
{
//настройки формата данных ЦАП аудио устройства
WaveFormat.wFormatTag=WAVE_FORMAT_PCM; //несжатые данные
WaveFormat.nChannels=Channels; //количество каналов записи
WaveFormat.nSamplesPerSec=FreqDis; //частота дискретизации
WaveFormat.nBlockAlign=Channels; //выравнивание блока
WaveFormat.nAvgBytesPerSec=FreqDis; //8000*1 байт в секунду
WaveFormat.wBitsPerSample=BitPesSempl; //разрядность отсчёта
WaveFormat.cbSize=0;
//инициировать устройства и выделить память для буферов
buf1In=new byte[DinBufSize];
buf2In=new byte[DinBufSize];
buf3In=new byte[DinBufSize];
buf4In=new byte[DinBufSize];
//открываем устройство записи
MMRESULT mmRes =waveInOpen(&hWaveIn, WAVE_MAPPER, &WaveFormat,(DWORD)FullBuf, 0L, CALLBACK_FUNCTION);
if(mmRes != MMSYSERR_NOERROR)
{
char text[MAX_PATH];
waveInGetErrorText( mmRes, text, MAX_PATH);
ShowMessage(text);
return;
}
//в заголовки буферов добавляем указатели и размеры
WaveHdrIn1.lpData=buf1In;
WaveHdrIn1.dwBufferLength=DinBufSize;
WaveHdrIn2.lpData=buf2In;
WaveHdrIn2.dwBufferLength=DinBufSize;
WaveHdrIn3.lpData=buf3In;
WaveHdrIn3.dwBufferLength=DinBufSize;
WaveHdrIn4.lpData=buf4In;
WaveHdrIn4.dwBufferLength=DinBufSize;
//Фиксируем буферы в памяти
waveInPrepareHeader(hWaveIn,&WaveHdrIn1,sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn,&WaveHdrIn2,sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn,&WaveHdrIn3,sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn,&WaveHdrIn4,sizeof(WAVEHDR));
}
Начать чтение из аудиоустройства
void __fastcall TForm1::SB_MicONClick(TObject *Sender)
{
//передаём буферы в очередь устройства записи
waveInAddBuffer(hWaveIn,&WaveHdrIn1,sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn,&WaveHdrIn2,sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn,&WaveHdrIn3,sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn,&WaveHdrIn4,sizeof(WAVEHDR));
//запустить устройство
waveInStart(hWaveIn);
}
Завершить чтение из аудиоустройства
void __fastcall TForm1::SB_MicOFFClick(TObject *Sender)
{
//остановить устройство записи
waveInStop(hWaveIn);
}
В итоге в калбэк функции мы получим буферы сэмплов, которыми можем манипулировать по своему усмотрению.