VR
Virtual Reality On-line   Журнал
Новости   |     Журнал    |    Хаkер    |     Магазин   |   Проекты
[   Вход    ]
[Kарта сайтa]

[ Download  ]
[  Конкурс  ]
[ Анекдоты  ]
[  Ссылки   ]
[  Реклама  ]
[ Почтальон ]
[ О проекте ]






TopList
Программирование звука.
Пример воспроизведения WAV файла
:
Logo

Мы уже знаем основные функции воспроизведения звуковых данных, знаем формат WAV файла, пора бы написать реальный пример. Сегодня мы выведем звуковые данные в колонки. Пример будет чисто демонстрационным и работает он только в Win9x. В NT наша прога непокатит.

На рисунке 1 показана форма сегодняшней проги. Я понимаю, что ты не станешь писать всё что я говорю своими руками, но я всё же объясню внутренности примера. Это тебе пригодится.
Logo
Рис 1. Форма

Самое главное - подключить модуль mmsystem в раздел uses. В этом модуле объявлены все мультимедиа функции.

А теперь переходим к примеру. Он достаточно большой и его изучение отнимет очень много времени. Если ты сможешь с ним разобратся, то поймёшь достаточно много тонкостей в программировании. Я здесь использовал очень много приёмов, которые мы ещё не обсуждали, но в следующем номере я постараюсь восполнить эту дыру. А начнём мы как всегда с загрузки, а именно с обработчика OnShow:

procedure TSounderForm.FormShow(Sender: TObject);
begin
 EnableBut(false);
 FillDevice;
 RestartPlay:=false;
end;

EnableBut - моя функция, которая делает недоступными кнопки воспроизведения, потому что ещё не загружен звуковой файл. Как только пользователь выберет WAV файл, мы снова сделаем эти кнопки доступными.

FillDevice - моя функция, которая определяет возможности звуковухи. Давай рассмотрим её подробнее:

procedure TSounderForm.FillDevice;
var
 pwoc:TWaveOutCaps;// Объявляю переменную типа TWaveOutCaps
begin
 //Запросить свойства звуковухи
 waveOutGetDevCaps(WAVE_MAPPER, @pwoc, sizeof(pwoc));
 // Если звуковая поддерживает изменение звука, то ...
 if (pwoc.dwSupport and WAVECAPS_VOLUME)>0 then
  begin
   // Делаю доступными регуляторы громкости.
   VolumeLBar.Enabled:=true;
   VolumeRBar.Enabled:=true;
  end;
end;

Здесь я использовал пока ещё неизвестную нам функцию waveOutGetDevCaps. Но это только пока. Сейчас мы разберёмся с этим монстром. Она выглядит так:

  Для С/С++
   MMRESULT waveOutGetDevCaps(
    UINT uDeviceID,    //Указатель на устройство воспроизведения
    LPWAVEOUTCAPS pwoc,//Структура, куда будут записаны данные
    UINT cbwoc         //Размер структуры
   );

  Для Delphi
   function waveOutGetDevCaps(
    uDeviceID: UINT;      //Указатель на устройство воспроизведения
    lpCaps: PWaveOutCaps; //Структура, куда будут записаны данные
    uSize: UINT           //Размер структуры
   ): MMRESULT; stdcall;

Второй параметр - структура WAVEOUTCAPS. Она выглядит так:

  Для С/С++

   typedef struct {
    WORD      wMid;                //Идентификатор производителя
    WORD      wPid;                //Идентификатор устройства
    MMVERSION vDriverVersion;      //Версия драйвера
    CHAR      szPname[MAXPNAMELEN];//Название устройства
    DWORD     dwFormats;           //Поддерживаемые форматы
    WORD      wChannels;           //Количество каналов
    WORD      wReserved1;          //Зарезервировано
    DWORD     dwSupport;           //Свойства
   } WAVEOUTCAPS;

  Для Delphi

   PWaveOutCapsA = ^TWaveOutCapsA;
   PWaveOutCapsW = ^TWaveOutCapsW;
   PWaveOutCaps = PWaveOutCapsA;
   {$EXTERNALSYM tagWAVEOUTCAPSA}
   tagWAVEOUTCAPSA = record
    wMid: Word;                 //Идентификатор производителя
    wPid: Word;                 //Идентификатор устройства
    vDriverVersion: MMVERSION;  //Версия драйвера
    szPname: array[0..MAXPNAMELEN-1] of AnsiChar; //Название устройства
    dwFormats: DWORD;           //Поддерживаемые форматы
    wChannels: Word;            //Количество каналов
    dwSupport: DWORD;           //Свойства
   end;

Самым интересным для нас является последний параметр - dwSupport . Он может принимать значения:

  • WAVECAPS_LRVOLUME - поддерживается изменение правого и левого каналов.
  • WAVECAPS_PITCH - Поддерживаются подтяжки
  • WAVECAPS_PLAYBACKRATE - поддерживается playback rate.
  • WAVECAPS_SYNC - драйвер сихронный и будет блокировать работу пока играет музыка.
  • WAVECAPS_VOLUME - поддержка громкости.
  • WAVECAPS_SAMPLEACCURATE - возвращает позицию.

Параметр wChannels может быть 1 (для моно) и 2 (для стерео).

Параметр dwFormats может быть комбинацией следующих значений:

  • WAVE_FORMAT_1M08 - 11.025 kHz, mono, 8-bit
  • WAVE_FORMAT_1M16 - 11.025 kHz, mono, 16-bit
  • WAVE_FORMAT_1S08 - 11.025 kHz, stereo, 8-bit
  • WAVE_FORMAT_1S16 - 11.025 kHz, stereo, 16-bit
  • WAVE_FORMAT_2M08 - 22.05 kHz, mono, 8-bit
  • WAVE_FORMAT_2M16 - 22.05 kHz, mono, 16-bit
  • WAVE_FORMAT_2S08 - 22.05 kHz, stereo, 8-bit
  • WAVE_FORMAT_2S16 - 22.05 kHz, stereo, 16-bit
  • WAVE_FORMAT_4M08 - 44.1 kHz, mono, 8-bit
  • WAVE_FORMAT_4M16 - 44.1 kHz, mono, 16-bit
  • WAVE_FORMAT_4S08 - 44.1 kHz, stereo, 8-bit
  • WAVE_FORMAT_4S16 - 44.1 kHz, stereo, 16-bit

Теперь перейдём к загрузке wav файла. По нажатию кнопки "Открыть" происходит следующее:

procedure TSounderForm.OpenButtonClick(Sender: TObject);
begin
 //Сначала, на всякий случай остановим воспроизведение
 StopButtonClick(nil);

 DataSize:=0; // Обнуляю размер данных
 if OpenDialog1.Execute then //Показать окно открытия файла
  OpenWaveFile; //Если файл выбран, то открыть его
 WavFileName:=OpenDialog1.FileName; //Сохраняем имя файла

 //Изменяем заголовок окна.
 Caption:='Sounder - '+ExtractFileName(WavFileName);

 //Показываю параметры звуковых данных. 
 if wfx.nChannels=1 then
  ChanelsBox.Text:='Моно'
 else
  ChanelsBox.Text:='Стерео';
 FreqBox.Text:=IntToStr(wfx.nSamplesPerSec);
 BitsBox.Text:=IntToStr(wfx.wBitsPerSample);
 Label7.Caption:=IntToStr(wfx.nAvgBytesPerSec);
end;

Здесь можно разобратся со всем происходящим по коментариям. В принципе, ничего особенного не происходит. Самое интересное будет в процедуре OpenWaveFile. Вот её мы сейчас и будем разглядывать. Если ты прочитал предыдущую статью о программировании звука и статью о формате файла WAV, то и она покажется тебе достаточно лёгкой.

procedure TSounderForm.OpenWaveFile;
var
 hFile:THandle;
 Str:array [0..255] of char;
 TempStr:String;
 BytesReaded,Size:DWORD;
begin
 //Если мы уже открывали WAV файл, то закрыть предыдущий
 if w_hData<>0 then
  CloseHandle(w_hData);

 //Открыть файл для чтения. Это достаточно продвинутая функция и мы
 //познакомимся с ней отдельно.
 hFile:=CreateFile(PChar(OpenDialog1.FileName),GENERIC_READ,
           FILE_SHARE_READ,nil,OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE,0);

 //Если файл открыт нормально, то продолжаем.
 if hFile=INVALID_HANDLE_VALUE then exit;

 //С TRY мы познакомимся в следующий раз
 //Пока скажу, что он работает, как Begin, а вместо end
 //нужно ставить finally + операторы + end;
 //или except + операторы + end;
 try
  //Получаем размер файлы с помощью GetFileSize
  wFileSize:=GetFileSize(hFile,nil);
  try
   //Устанавливаем позицию в файле на 8 байт от начала
   SetFilePointer(hFile,8,nil,FILE_BEGIN);
   //Прочитаем начиная от этой позиции 8 байт
   ReadFile(hFile,Str,8,BytesReaded,nil);
   Str[7]:=#0;//Обнуляю седьмой байт
   TempStr:=Str;//Перегоняю результат в строку

   //Если прочитанная строка не равна 'WAVEfmt', то это не WAV
   if TempStr<>'WAVEfmt' then
    begin
     //Вывожу сообщение об ошибке
     Application.MessageBox('Ошибка заголовка','Error',
                                MB_OK+MB_ICONINFORMATION);
     CloseHandle(hFile);//Закрываю файл
     exit;              //Выход из процедуры
    end;

   //Читаем размер следующего блока
   ReadFile(hFile,Size,Sizeof(Size),BytesReaded,nil);
   //Обнуляем структуру wfx
   ZeroMemory(@wfx,sizeof(wfx));

   //Если размер следующего блока равен размеру структуры,
   //То читаем структуру. Иначе выдаём ошибку
   if Size=sizeof(PCMWAVEFORMAT) then
    begin
     ReadFile(hFile,wfx,Size,BytesReaded,nil);
    end
   else
    begin
     MessageBox(Handle, 'Формат не поддерживается', 'Error',
                                    MB_OK+MB_ICONEXCLAMATION);
     exit;
    end;

   wfx.cbSize:=0;
   ReadFile(hFile,Str,4,BytesReaded,nil);

   //Читаем размер следующего блока
   ReadFile(hFile,DataSize,4,BytesReaded,nil);

   //Далее идут звуковые данные, поэтому сохраняем текущую позицию
   //в DataOffset, чтобы можно было сразу перейти к данным.
   DataOffset:=SetFilePointer(hFile,0,nil,FILE_CURRENT);

   //Создаём образ файла в памяти с помощью CreateFilemapping
   w_hData:=CreateFilemapping(hFile,nil,PAGE_READONLY,0,
       Integer(wFileSize),nil);
   //Закрываем файл
   CloseHandle(hFile);
   hFile:=INVALID_HANDLE_VALUE;
  except
   Application.MessageBox('Формат не поддерживается','Error',
                                       MB_OK+MB_ICONINFORMATION);
   CloseHandle(hFile);
   hFile:=INVALID_HANDLE_VALUE;
  end;
 finally
  if hFile<>INVALID_HANDLE_VALUE then CloseHandle(hFile);

  if DataSize>0 then
   begin
    EnableBut(true);
    InitSoundControls;
   end
  else
   EnableBut(false);
 end;
end;

WAV файл загружен. Теперь переходим к воспроизведению. По нажатию кнопки "Играть" происходит следующее:

procedure TSounderForm.PlayButtonClick(Sender: TObject);
begin
 //Если была нажата пауза, то продолжить воспроизведение.
 if ((w_Pause)and (w_hPlay<>0)) then
  begin
   waveOutRestart(w_hPlay);
   w_Pause:=not w_Pause;
   exit;
  end;

 //Если данные воспроизводятся, то остановить вывод
 if w_hPlay<>0 then StopButtonClick(nil);

 //Включить таймер
 SoundTimer.Enabled:=true;
 w_Pause:=false; w_Restart:=false; w_Stop:=false;
 w_PlayPosition:=PositionBar.Position*1000;
 w_RestartPosition:=PositionBar.Position*1000;

 //Устанавливаю устройство поумолчанию.
 w_DeviceID:=Integer(WAVE_MAPPER);

 //Запускаю поток воспроизведения данных.
 CyDSounder1:=TSoundPlayer.Create(true);
 //Устанавливаю высокий приоритет
 CyDSounder1.Priority:=tpHigher;
 CyDSounder1.Resume;
end;

При запуске потока я вызываю только одну процедуру, которая будет отвечать за вывод звуковых данных:

procedure TSoundPlayer.Execute;
begin
 Process;
end;

Давай мельком посмотрим на эту процедуру:

procedure TSoundPlayer.Process;
const
 DEFAULT_BLOCKSIZE=64*1024; //Размер 1-го блока
var
 BlockSize,ii,shift,size:Integer;
 si:TSystemInfo;
 curHdr:WAVEHDR;
 ofs:Int64;
 p:PSoundArray;
begin
 BlockSize:=DEFAULT_BLOCKSIZE;
 GetSystemInfo(si); //Получаю системную информацию
 ZeroMemory(@Header,sizeof(Header)); //Обнуляю заголовок

 hEvent:=CreateEvent(nil,false,false,nil);
 try
  //Открываю звуковуху
  WaveOutCheck(waveOutOpen(@SounderForm.w_hPlay, SounderForm.w_DeviceID,
                           @SounderForm.wfx, hEvent, 0,CALLBACK_EVENT));

  //Выделяю память для данных
  Header[0].lpData:=VirtualAlloc(nil,round((BlockSize*2+
                  Integer(si.dwPageSize)-1)/si.dwPageSize*si.dwPageSize),
                  MEM_RESERVE+MEM_COMMIT,PAGE_READWRITE);
  if Header[0].lpData=nil then exit;

  //Выделенную память разделяю на два заголовка
  Header[1].lpData:=Header[0].lpData+BlockSize;
  Header[0].dwBufferLength:=BlockSize;
  Header[1].dwBufferLength:=BlockSize;

  //Подготавливаю заголовки
  waveOutCheck(waveOutPrepareHeader(SounderForm.w_hPlay,
           @Header[0],sizeof(waveHDR)));
  waveOutCheck(waveOutPrepareHeader(SounderForm.w_hPlay,
          @Header[1],sizeof(waveHDR)));
  ii:=0;
  if SounderForm.DataSize<=BlockSize then ResetEvent(hEvent);

  //Запускаю цикл воспроизведения
  while SounderForm.w_PlayPositionSounderForm.DataSize-
         SounderForm.w_PlayPosition then
     BlockSize:=SounderForm.DataSize-SounderForm.w_PlayPosition;

    //Расчитываю размер данных
    curhdr.dwBufferLength:=BlockSize;
    ofs:=SounderForm.w_PlayPosition+SounderForm.DataOffset;
    shift:=(ofs mod si.dwAllocationGranularity);
    ofs:=ofs-shift;
    size:=(BlockSize+ofs+si.dwPageSize-1);
    size:=size-(size mod Integer(si.dwPageSize));
    if size>SounderForm.wFileSize-ofs then size:=SounderForm.wFileSize-ofs;

    //Загружаю из памяти файла звуковые данные
    p:=MapViewOfFile(SounderForm.w_hData,FILE_MAP_READ, 
        ofs shr 32 and $FFFFFFFF,ofs and $FFFFFFFF, Size);
    try
     //Копирую в заголовок
     CopyMemory(curhdr.lpData, @p[shift], min(size-shift,BlockSize));
    finally
     UnMapViewOfFile(p);
    end;

    //Воспроизвожу.
    waveOutCheck(waveOutWrite(SounderForm.w_hplay,@curhdr,sizeof(waveHDR)));
    //Ожидаю окончания воспроизведения
    if (WAIT_TIMEOUT=WaitForSingleObject(hEvent,10000)) then
     begin
      if not SounderForm.w_Pause then
       begin
        MessageBox(SounderForm.Handle,'Ошибка воспроизведения','Ошибка',
                      MB_OK+MB_ICONINFORMATION);
        break;
       end
      else
       WaitForSingleObject(hEvent,INFINITE);
     end;

    //Если нажат стоп, то выход
    if SounderForm.w_Stop then ClosePlay;

    //Если отпущена пауза, то востановить воспроизведение
    if SounderForm.w_Restart then
     begin
      waveOutReset(SounderForm.w_hPlay);
      SounderForm.w_Restart:=false;
      SounderForm.w_PlayPosition:=SounderForm.w_RestartPosition;
      BlockSize:=DEFAULT_BLOCKSIZE;
      if SounderForm.DataSize>BlockSize then SetEvent(hEvent);
      continue;
     end;
    //Новая позиция для чтения следующих данных
    SounderForm.w_PlayPosition:=SounderForm.w_PlayPosition+BlockSize;
   end;
 finally
  //Ожидаю окончания воспроизведения
  WaitForSingleObject(hEvent,20000);
  //Закрываю воспроизведение
  ClosePlay;
 end;
end;

На сегодня хватит. Я просто уверен, что ты не до конца понял, что здесь происходит. Пример получился слишком насыщеный новыми вещами. В следующем месяце я постараюсь объяснить как можно больше из того, что здесь было нового. А пока что наслождайся музыкой.

 Исходники примера забирай здесь


Design by FMk group ©
Copyright©: Horrific aka Флёнов Михаил ©