AFCViewerвид изнутри

Работающая в потоке, процедура PlayExecute представляет собой бесконечный цикл, в котором вызывается функция ожидания WaitForMultipleObjects. Когда курсоры воспроизведения и ввода оказываются на одной из позиций уведомления своих буферов, функция ожидания возвращает системный номер соответствующего события. Вычитая из него системную константу WAIT_OBJECT_0, получаем индекс события в массиве Events, передававшимся в функцию ожидания во втором параметре. Далее, в зависимости от значения индекса, определяем, от какого буфера "пришло" событие (уведомление) - вывода (воспроизведения) или ввода. Обработка событий буфера воспроизведения подробно рассмотрена в статье по устройству SineMusit.

interface
  ...
var
  Cap   : array of array of SmallInt;  // данные из половины входного буфера
  CapIs : boolean = false;             // наличие необработанных данных в Cap
  ...

implementation

var
  Events     : array[0..3] of THandle;             // дескрипторы событий для вывода и ввода
  EventsPos  : array[0..1] of TDSBPositionNotify;  // позиции уведомлений воспроизведения
  CEventsPos : array[0..1] of TDSBPositionNotify;  // позиции уведомлений ввода
  blank      : boolean;  // буфер еще пуст (первое событие после запуска)
  ...

procedure PlayExecute;
var
  evt    : DWORD;  //  номер события
  offset : DWORD;  //  положение начала сегмента относительно начала буфера
begin
  while true do begin
    //  ожидание события
    evt:= WaitForMultipleObjects(4, PWOHandleArray(@Events), false, INFINITE);
    //  определение индекса события
    evt:= evt - WAIT_OBJECT_0;
    //  произошло ли событие вывода иди ввода?
    if evt < 2 then begin
      //  событие вывода: определение заполняемой половины буфера
      if evt = 0
        then offset:= EventsPos[1].dwOffset   // заполняется вторая половина
        else offset:= 0;                      // заполняется первая половина
      //--------------------------------------------------
      FillBufferSegment(offset, EventsPos[1].dwOffset);
      //--------------------------------------------------
    end
    else begin
      //  событие ввода
      if blank then begin // сразу после старта буфер ещё пуст
        blank:= false;
        Continue;
      end;
      if CapIs then   // предыдущие данные еще не обработаны
        Continue;
      evt:= evt - 2;
      //  определение копируемой половины буфера
      if evt = 0
        then offset:= CEventsPos[1].dwOffset  // копируется вторая половина
        else offset:= 0;                      // копируется первая половина
      //--------------------------------------------------
      ReadBufferSegment(offset, СEventsPos[1].dwOffset);
      CapIs:= true;
      //------------------------------------------------------
    end;
  end;
end;

Обработка событий ввода начинается с проверки значения глобальной переменной blank. Необходимость существования этой переменной объясняется тем, что самое первое событие, полученное после старта ввода, "оставляет позади себя" еще не заполненную входными данными половину буфера, читать из которой нечего. При старте ввода этой переменной присваивается значение true, здесь же она "сбрасывается" и больше на работу не влияет. Далее проверяется, завершена или нет обработка предыдущих данных. Поскольку в общем случае процесс анализа принятых данных может быть сколь угодно сложным и соответственно долгим, то его лучше выполнять асинхронно, в отдельном потоке. Здесь же, в этом потоке, делается лишь копирование только что оцифрованного звука, из очередной половины буфера ввода DirectSound в обычный массив, не имеющий никаких ограничений по времени хранения своего содержимого. Флаг CapIs устанавливается после копирования здесь, а сбрасывается в потоке обработки сразу после ее завершения. Копирование выполняется в массив Cap процедурой ReadBufferSegment. Ее первый параметр - смещение, в байтах, начала заполняемого участка (сегмента) относительно начала буфера. Второй параметр - длина участка. Разумеется, перед вызовом процедуры надо определиться с тем, какую половину буфера копировать. Это делается по индексу полученного события в массиве СEventsPos. Сама процедура ReadBufferSegment выглядит так:

interface
  ...
var
  Cap   : array of array of SmallInt;  // данные из половины входного буфера
  CapIs : boolean = false;             // в Cap имеются необработанные данные
  ...

implementation

var
  //  формат буфера
  WFX : TWaveFormatEx =
    ( wFormatTag      : 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
  ...

procedure ReadBufferSegment(offset, l : DWORD);
  // offset, l - положение и длина в байтах получаемого сегмента буфера
var
  ad1, ad2 : pointer;  // адес сегмента буфера
  l1, l2   : DWORD;    // длина сегмента буфера в байтах
  i,j : DWORD;
  ls  : DWORD;         // длина сегмента буфера в минимальных блоках данных
begin
  //  блокирование буфера
  ad1:= nil;
  ad2:= nil;
  hr:= DSCB.Lock(offset, l, @ad1, @l1, @ad2, @l2, 0);
  if failed(hr) then begin
    ShowMessage('Ошибка при блокировании входного звукового буфера');
    halt;
  end;
  //--- копирование сегмента в массив Cap ---
  if not Assigned(Cap) or (Length(Cap) <> WFX.nChannels) then
    SetLength(Cap, WFX.nChannels);
  ls:= l1 div WFX.nBlockAlign;
  for j:=0 to WFX.nChannels-1 do begin
    if not Assigned(Cap[j]) or (Length(Cap[j]) <> ls) then
      SetLength(Cap[j], ls);
    for i:=0 to ls-1 do
      Cap[j,i]:= PSmallInt(DWORD(ad1) + i*WFX.nBlockAlign + 2*j)^;
  end;
  //  разблокирование буфера
  hr := DSCB.UnLock(ad1, l1, ad2, l2);
  if failed(hr) then begin
    ShowMessage('Ошибка при разблокировании входного звукового буфера');
    halt;
  end;
end;

В общем виде здесь всего три дейсвия: блокирование участка буфера методом Lock, копирование содержимого в массив Cap, разблокирование участка методом Unlock. Все это уже знакомо нам по описанию работы буфера воспроизведения. Отмечу лишь одно обстоятельство - при копировании попутно изменяется расположение выборок в массиве. Вместо чередования - левый. правый, левый, правый..., принятого для буферов DirectSound, в массиве Cap выборки одного канала располагаются последовательно, а выбор самого канала осуществляется по первому индексу. Для последующего вычисления амплитуды сигнала такое расположение немного удобнее. На этом инициализация DirectSound для AFCViewer или какой-либо другой программы, испльзующей этот модуль DirectX как для ввода, так и для вывода звука, завершена. На четвертой странице этой статьи размещен полный исходный код модуля содержащего всё то, что рассмотрено выще и еще несколько процедур. Это немного упрощенная, для лучшей читаемости, версия того модуля, который реально используется в AFCViewer. Вы можете свободно использовать его в своих проектах.


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

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

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