AFCViewerвид изнутри

Программа написана на Delphi в рамках VCL Win32 с использованием DirectSound, входящего в состав библиотеки DirectX. Исходный код AFCViewer, в той своей части , которая обеспечивает взаимодействие с DirectSound, во многом дублирует соответствующий модуль другой программы проекта "Маленькие помощники" - SineMusit. Чтобы избежать ненужного повторения одного и того же текста, здесь рассмотрены только дополнительные, по сравнению с SineMusit, элементы. Если вы еще не читали статью: "SineMusit: вид изнутри", то начать лучше с нее, иначе идущий далее текст, может показаться непонятным.

За приём (в буквальном переводе - захват) звука со входа звуковой карты в DirectSound отвечает интерфейс IDirectSoundCapture. Последовательность действий при использовании этого интерфейса подобна той, что применялась при подготовке вывода звука, только немного проще. Сначала создается главный объект захвата звука:

var
  hr  : HRESULT;
  DSС : IDirectSoundCapture;
  ...
  hr := DirectSoundCaptureCreate(nil, DSC, nil);
  //  проверить успешность создания объекта
  if failed(hr) then
    ...      //  увы, создать объект не удалось

Все параметры функции DirectSoundCaptureCreate аналогичны соответствующим параметрам функции DirectSoundCreate. Первый параметр - GUID устройства захвата звука, второй - переменная создаваемого объекта, третий параметр должен быть равен nil.

В отличии от вывода звука, при вводе нет необходимости задавать уровень совместного доступа к звуковому оборудованию. Это понятно - принимать данные могут одновременно несколько программ, без всякого влияния друг на друга или на само оборудование. Поэтому следующий шаг - получение данных о возможностях имеющегося оборудования по вводу и сравнение их с минимальными требованиями AFCViewer. Для этого потребуется переменная типа TDSCCaps, представляющего собой запись:

type
  TDSCCaps = packed record
    dwSize    : DWORD;    // размер переменной
    dwFlags   : DWORD;
    dwFormats : DWORD;    // поддерживаемые форматы
    dwChannels: DWORD;
  end;

Интерес представляет поле dwFormats, в котором содержится совокупность флагов, соответствующих поддерживаемым форматам. Константа WAVE_FORMAT_4S16 позволяет проверить, способно ли оборудование работать на частоте дискретизации 44100 Гц при 16 битах на выборку. Само получение данных обеспечивает метод GetCaps главного объекта захвата:

var
  hr      : HRESULT;
  DSС     : IDirectSoundCapture;
  DSCCaps : TDSCCaps;
  ...
  //  подготовка переменной DSCCaps
  ZeroMemory(@DSCCaps, SizeOf(DSCCaps));
  DSCCaps.dwSize := SizeOf(DSCCaps);
  //  получение данных
  hr:= DSC.GetCaps(DSCCaps);
  if failed(hr) then
    ...      //  получить данные о возможностях оборудования по вводу не удалось
  if (DSCCaps.dwFormats and WAVE_FORMAT_4S16) = 0 then
    ...      //  слабая звуковая карта

Как обычно, при получении данных, предназначенная для них переменная передается в функцию полностью очищенной, за исключением, разумеется, поля dwSize. Результатом работы метода GetCaps станет заполнение, переданной ему переменной DSCCaps, реальными значениями. Теперь можно анализировать поля переменной и по результатам анализа предпринимать какие-то дейсвия.

Следующий шаг - создание входного звукового буфера. Для описания параметров буфера ввода в DirectSound имеется тип TDSCBufferDesc, как обычно, предстающий собой запись:

type
  TDSCBufferDesc = packed record
    dwSize         : DWORD;          // размер самой записи в байтах
    dwFlags        : DWORD;          // комбинация флагов определяющая возможности буфера
    dwBufferBytes  : DWORD;          // размер буфера в байтах
    dwReserved     : DWORD;
    lpwfxFormat    : PWaveFormatEx;  // указатель на переменную типа TWaveFormatEx
  end;

В последнем поле содержится указатель на переменную, определяющую формат буфера ввода. Тип WaveFormatEx уже описывался в статье по программе SineMusit. Создается буфер метдом CreateCaptureBuffer главного объекта захвата:

const
  NSamp = 10000;    //  размер буфера в минимальных блоках данных
var
  //  формат буфера
  WFX : TWaveFormatEx =
    ( wFormatTag      : WAVE_FORMAT_PCM;        // тип формата, для DirectSound всегда WAVE_FORMAT_PCM
      nChannels       : 2;                      // количество каналов
      nSamplesPerSec  : 44100;                  // частота дискретизации
      nAvgBytesPerSec : 44100 * 2 * (16 div 8); // количество байтов в секунду (nBlockAlign * nSamplesPerSec)
      nBlockAlign     : 2 * (16 div 8);         // размер минимального блока данных в байтах
      wBitsPerSample  : 16;                     // количество бит на выборку (8, 16, ...)
      cbSize          : 0 );                    // для DirectSound всегда 0
  //  описание свойств буфера
  CBufDesc : TDSCBufferDesc =
    ( dwSize          : SizeOf(TDSCBufferDesc);
      dwFlags         : 0;
      dwBufferBytes   : NSamp * 2 * 2;  //  размер буфера в байтах = NSamp * кол-во каналов * байт на выборку
      dwReserved      : 0;
      lpwfxFormat     : @WFX );
  hr   : HRESULT;
  DSC  : IDirectSoundCapture;           //  объект DirectSoundCapture
  DSCB : IDirectSoundCaptureBuffer;     //  буфер ввода (захвата)
    ...
  //  создание входного звукового буфера
  hr := DSC.CreateCaptureBuffer(CBufDesc, DSCB, nil);
  if failed(hr) then begin
    ...    //  Ошибка при создании буфера ввода

В первом параметре метода передается переменная с описанием свойств буфера. Второй параметр - сама переменная создаваемого объекта. Третий параметр должен быть равен nil. Ввод звука в AFCViewer осуществляется по тому же самому принципу, который уже рассматривался в статье по SineMusit. То есть буфер представляется как бы разделенным на две половины и замкнутым в кольцо. В начале каждой половины установлена позиция уведомления. Когда курсор ввода досигает позиции уведомления, возникнет системное событие, которое можно принять и обработать. Следовательно, следующий шаг - создание для буфера ввода интерфейса уведомлений и установка их позиций.

var
  hr         : HRESULT;
  DSCB       : IDirectSoundCaptureBuffer;
  DSCN       : IDirectSoundNotify;
  Events     : array[0..3] of THandle;             // дескрипторы событий для вывода [0..1] и ввода [2..3]
  CEventsPos : array[0..1] of TDSBPositionNotify;  // позиции уведомлений буфера ввода
    ...
  //  создание интерфейса уведомлений
  hr := DSCB.QueryInterface(IID_IDirectSoundNotify, DSCN);
  if failed(hr) then
    ...        //  ошибка при создании интерфейса уведомлений для буфера ввода
  //  задание позиций уведомлений
  for i:=0 to High(CEventsPos) do begin
    Events[i+2] := CreateEvent(nil, false, false, nil);
    CEventsPos[i].hEventNotify := Events[i+2];
    CEventsPos[i].dwOffset := i*(CBufDesc.dwBufferBytes div 2);
  end;
  //  Включение уведомлений
  hr := DSCN.SetNotificationPositions(2, @CEventsPos[0]);
  if failed(hr) then
    ...        //  ошибка при включении уведомлений
  end;

Создается интерфейс уведомлений методом QueryInterface буфера ввода. Первый параметр метода - идентификатор создаваемого интерфейса, а второй - переменная создаваемого объекта. Далее, с помощью системной функции CreateEvent создаются события буфера ввода. Массив дескрипторов событий в AFCViewer - общий для событий вывода и ввода, при этом события вывода имеют индексы 0 и 1, а события ввода индексы 2 и 3. Общий массив нужен для того, чтобы можно было использовать только одну ожидающую функцию и соответственно только один поток для работы с обоими буферами - вывода и ввода. А вот массивы, содержащие позиции уведомления, у каждого из этих буферов свои. Желание использовать как можно меньше потоков и ожидающих функций, объясняется просто - так меньше "накладные расходы" операционной системы по обслуживаню программы.

Последнее действие, при инициализации DirectSound в AFCViewer, - создание и запуск потока, в котором будут перехватываться события обоих буферов, полностью совпадает с тем, что уже рассмаривалось в статье по SineMusit. Вообще, вся часть AFCViewer выполняющая вывод сигнала средствами DirectSound, почти полностью совпадает с таковой в SineMusit.

var
  PlayThreadHandle : THandle;
  PlayThreadID     : DWORD;
  ...
  PlayThreadHandle := CreateThread(nil, 0, TFNThreadStartRoutine(@PlayExecute), nil, 0, PlayThreadID);

Поток завершается и уничтожается самой операционной системой при завершении программы. В третьем параметре передается адрес процедуры PlayExecute, которая как раз и принимает события позиций уведомлений.


Назад: к общему описанию программы
Голос народа: отзывы, пожелания, мнения
1
2 3 4 5

Если Вы хотите поддержать разработку бесплатных программ и в частности этот сайт, то сделать это можно не только "монетами". Компьютерное оборудование, или как сейчас говорят "железо", которое Вы, по тем или иным причинам сочли уже неподходящим для себя, возможно сможет еще поработать. Правда, не во всех видах оборудования есть необходимость ...

Москва, 2013-2020 гг., © ZHarNS58
623