Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений
.pdf
Программы записи и воспроизведения звука 151
ку включены функции «разподготовки» блока памяти waveInUnprepareHeader и
закрытия устройства. Нажатие на третью кнопка выполняет знакомые нам
операции по открытию устройства вывода звука, подготовку того же блока памяти, но для устройства вывода, непосредственно воспроизведения и удаления
блока из памяти. Кроме того, добавлена четвертая кнопка, закрывающая программу.
Перейдем к проектированию приложения. Как обычно, создайте новый
проект и сохраните его в выбранном месте. На форму поместите четыре кнопки и, если хотите, переименуйте их так, как это представлено на предыдущем
рисунке. С палитры VCL Standard возьмите компонент Группа радиокнопок
(RadioGroup)итакже установите его на форму. Эта группа будет предназначаться для переключения режимов Моно и Стерео (1 и 2 канала соответственно). В инспекторе объектов для этого компонента установите свойство Caption
(название группы) в Mono/Stereo. Для того чтобы задать имена радиокнопок в
инспекторе объектов в свойстве Items, нажмите кнопку справа с изображением
многоточия. Откроется окно небольшого редактора строк, где нужно впечатать
две строки с названиями, как это видно на следующем рисунке.
Выбранная радиокнопка â группе определяет значение свойства
ItemIndex. Если выбрана первая кнопка, это свойство имеет значение 0, если
вторая — 1. Исходно ни одна из кнопок не выбрана, ItemIndex имеет значение — 1. Сделаем так, что по умолчанию считается выбранной первая кнопка — режим Mono. Для этого на этапе проектирования свойство ItemIndex установим в 0, как это показано в левой части рис. 4.8.
Точно так же создадим и настроим вторую группу радиокнопок 8/16 bit
для выбора разрядности оцифровки. По умолчанию примем, что разрядность
составляет 8 бит. Установив на стадии проектирования предопределенные состояния выбора кнопок, необходимо позаботиться о том, чтобы были установлены и соответствующие значения переменных в программе. Именно по-
Ðèñ. 4.8

152 Глава 4. Запись, обработка и воспроизведение звука
этому при объявлении глобальных переменных razrad è kanal они инициализированы величинами8и1.Вначале тела обработчика нажатия первой
кнопки на форме находятся два условных выражения, которые срабатывают,
если значения индексов групп радиокнопок изменились по отношению к исходных (с 0 на 1). При этом разрядность оцифровки становится равной 16,
а число каналов 2.
Для выбора частоты дискретизации мы воспользуемся еще одним визуальным компонентом ListBox. Этот компонент позволяет выбрать одну из заданных строк. Настройка компонента очень похожа на настройку радиокнопок. С помощью редактора строк также создается список в данном случае из 4
строчек 8000, 11 025, 22 050 и 44 100. В зависимости от номера выбранной
строки индекс принимает значение от 0 до 3. Существенное отличие от групп
радиокнопок состоит в том, что на этапе проектирования нельзя установить
предопределенное значение индекса, а следовательно, и выбранную строку по
умолчанию. В этой связи индекс приходится установить в программе «вручную». Мы это сделали выражением присваивания ListBox1->ItemIndex = 0;
прямо в функции генерации формы. Таким образом, предопределенным значением индекса будет 0, а выбором по умолчанию будет первая строка 8000.
В обработчике первой кнопки установлен переключатель, который ставит в
соответствие индексу ListBox значение переменной diskret.
Еще одно новшество заключается в том, что мы задаем возможность установки максимальной длительности записи, выраженную в секундах. Для этого
применим компонент Edit. Настройка компонента совсем проста — можно
установить предопределенное значение и работать со значением в поле редактирования прямо как с числом (только если это число целое). Введенную длительность необходимо преобразовать в размер буфера. Нетрудно убедиться,
что пересчет должен быть выполнен в выражении присваивания buf =
kanal*razrad/8*tau*diskret;. В заключении отметим без комментариев, что для
отображения надписей «Частота дискретизации» и «Максимальная длительность (сек.)» использованы визуальные компоненты Label. Теперь читателю
осталось реализовать проект и с большим комфортом «поиграть» с различными режимами звукозаписи.
Программируем сжатие аудиоинформации
В инструментарии Windows API довольно широко представлены средства
обработки звуковой информации. Существует специальный аппарат функций,
предназначенных для преобразования звуковых данных, называемый диспетчером сжатия звука — Audio Compression Manager (ACM). На самом деле возможности ÀÑÌ более широкие, нежели только сжатие звука. Система может
преобразовывать форматы звука, выполнять фильтрацию на основе изменения амплитудно-частотной характеристики (АЧХ) сигнала и производить другие операции со звуком. Сам по себе диспетчер действительно является только управляющим задачами обработки. Собственно преобразованием данных
занимаются подключаемые программы, называемые, на наш взгляд не вполне

Программируем сжатие аудио информации 153
корректно, драйверами ÀÑÌ. В этой связи возможности ÀÑÌ во многом определяются тем, какие драйверы инсталлированы в вашей системе, и набор
этих драйверов может быть всегда расширен.
 ÀÑÌ следует разделять два типа обработки звука. Первый — преобразование форматов. При этом изменяется способ представления цифрового звука
в блоке данных (файле или буфере памяти). Например, преобразование формата может быть применено при изменении кодирования импульсно-кодовой
модуляции ÐÑÌ â ADPCM. Если преобразование производится над файлом,
то файл изменяет свой формат, например, из .wav â .mp3. Преобразование
формата может сопровождаться или не сопровождаться изменением параметров звука: частоты дискретизации, разрядности оцифровки, количества каналов. В частности, даже в одном и том же формате уменьшение значений указанных параметров приводит к уменьшению объема звуковых данных (сжатию, компрессии), правда, с потерей качества. Конвертирование форматов
возможно и без потери качества, но при существенном сжатии. Второй тип
обработки звука — фильтрация. В этом варианте ÀÑÌ производит преобразование непосредственно самого оцифрованного звукового сигнала в пределах
одного и того же формата. Прежде всего, речь идет об изменении АЧХ. Например, «обрезание» высокочастотной части характеристики может слабо сказаться на субъективном восприятии звука и, в то же время, существенно
уменьшить объем данных в файле. Кроме того, к фильтрации относится усиление или ослабление сигнала, наложение на сигнал различных эффектов.
Программы сжатия и восстановления мультимедийной информации называются кодеками (кодировщиками и декодировщиками). Подключение новых
кодеков обеспечивает расширение возможностей системы ÀÑÌ.
ÀÑÌ является довольно «высокоинтеллектуальным» инструментом. Его
«творческие» способности проявляются в том, что он может самостоятельно
подобрать и подключить из имеющегося арсенала драйверы, наилучшим образом обеспечивающие заданное преобразование.
В простейшем случае работа менеджера ÀÑÌ выполняется в соответствии
со следующими этапами:
1. Аналогично процедуре записи звука создаются два блока в оперативной
памяти для исходных и преобразованных данных. Блоки снабжаются заголовками, содержащими характеристики звука, которые записываются в структуры типа WAVEFORMATEX точно так же, как и при записи. В блок-источник
записываются звуковые данные из блока записи в памяти, звукового файла
или даже «на лету» из устройства ввода.
2. С помощью функции acmStreamOpen создается так называемый поток
преобразования. В аргументах функции указываются либо структуры исходного и преобразованного формата, либо исходный формат и драйвер преобразования. В первом варианте необходимый драйвер находится системой автоматически (разумеется, если вообще находится).
3. Организуется структура предопределенного òèïà ACMSTREAM-
HEADER, называемая заголовком потока. В структуру заносятся некоторые
характеристики исходного и результирующего блоков, в том числе размеры

154 Глава 4. Запись, обработка и воспроизведение звука
буферов для исходных и преобразованных данных. Размер результирующего
буфера определяется с помощью функции acmStreamSize.
4. Блок, определенный в структуре заголовка, подготавливается (передается потоку) с помощью функции acmStreamPrepareHeader. Далее выполняется
непосредственно преобразование — функция acmStreamConvert, «разподготавливается» блок — функция AcmStreamUnprepareHeader и закрывается поток
преобразования — acmStreamClose.
5. Преобразованные данные из блока-приемника записываются в блок
памяти для воспроизведения или звуковой файл.
Теперь построим приложение, которое будет выполнять весьма скромную
задачу. Программа должна записать звук с микрофона в блок памяти в соответствии с заданными характеристиками в формате импульсно-кодовой модуляции. По окончании записи пользователь вводит новую частоту дискретизации, и приложение производит преобразование звука, оставаясь в том же
формате. Преобразованные данные содержатся в блоке в памяти, и пользователь может воспроизвести звук со старой и новой частотой дискретизации.
Интерфейс приложения будет выглядеть так, как это представлено на
рис. 4.9.
Ðèñ. 4.9
В представленной постановке задачи естественно разрабатывать приложение на основе программы, описанной в предыдущем разделе. Мы по-прежнему не имеем возможности подробно описывать такие детали, как все аргументы функций и поля структур, — всю необходимую информацию можно найти
в справочной системе C++ Builder. Однако основные моменты, касающиеся
использования ÀÑÌ в программе, будут освещены. Листинг основного текстового модуля программы представлен далее. Предварительно отметим, что
для преобразования интерфейса из предыдущей программы на форму необхо
димо добавить еще один компонент ListBox для выбора частоты оцифровки
-

Программируем сжатие аудио информации 155
результирующего блока. Конструирование и программирование компонента
полностью повторяет соответствующие действия для первого списка. Кроме
того, как можно видеть из интерфейса приложения, здесь добавлены еще две
кнопки CONVERT è PLAY NEW, а кнопка PLAY переименована в PLAY OLD.
Функции-обработчики для этих кнопок будут рассмотрены ниже.
//—————————————————————————————————————#include <VCL.h>
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <MMREG.H>
#include "msacm.h"
#include "Unit1.h"
#pragma hdrstop
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int tau, // Максимальная длительность
diskret,diskret1, // Частоты дискретизации
razrad=8, // Разрядности оцифровки
kanal=1; // Число каналов
long buf; // Размер буфера
HWAVEIN hWaveIn; // Указатель устройства ввода
HWAVEOUT hWaveOut; // Ключ устройства ввода
UINT DstBufferSize; // Размер буфера результата
HACMSTREAM Stream; // Ключ потока преобразования
WAVEFORMATEX SrcFmt,DstFmt;
//Структуры исходного и конечного потоков (PCM)
DWORD ConvertFlags; // Флаги преобразования ACM
BYTE *DstBuffer; // Указатель буфера порции данных результата
WAVEFORMATEX wfx,wfx1;
LPWAVEHDR lpHdr = new WAVEHDR;
LPWAVEHDR lpHdr1 = new WAVEHDR;
//—————————————————————————————————————__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ListBox1->ItemIndex = 2;
ListBox2->ItemIndex = 1;
}
//—————————————————————————————————————-
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(RadioGroup1->ItemIndex == 1) kanal = 2;
if(RadioGroup2->ItemIndex == 1) razrad = 16;
tau = StrToInt(Edit1->Text);
switch (ListBox1->ItemIndex)
{
case 0: diskret = 8000; break;

156 Глава 4. Запись, обработка и воспроизведение звука
case 1: diskret = 11025; break;
case 2: diskret = 22050; break;
case 3: diskret = 44100; break;
}
buf = kanal*razrad/8*tau*diskret;
lpHdr->lpData = (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, buf);
wfx.wFormatTag = WAVE_FORMAT_PCM;
int nC = wfx.nChannels = kanal;
int BPS = wfx.wBitsPerSample = razrad;
int nSPS = wfx.nSamplesPerSec = diskret;
int nBA = wfx.nBlockAlign = nC*BPS/8;
wfx.nAvgBytesPerSec = nSPS*nBA;
lpHdr->dwBufferLength = buf;//
waveInOpen(&hWaveIn, WAVE_MAPPER,&wfx,0L, 0L, 0L);
waveInPrepareHeader(hWaveIn, lpHdr,sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, lpHdr,sizeof(WAVEHDR));
waveInStart(hWaveIn);
}
//—————————————————————————————————————void __fastcall TForm1::Button2Click(TObject *Sender)
{
switch (ListBox2->ItemIndex)
{
case 0: diskret1 = 8000; break;
case 1: diskret1 = 11025; break;
case 2: diskret1 = 22050; break;
case 3: diskret1 = 44100; break;
}
waveInReset(hWaveIn);
waveInClose(hWaveIn);
waveInUnprepareHeader(hWaveIn, lpHdr,sizeof(WAVEHDR));
lpHdr->dwBufferLength = lpHdr->dwBytesRecorded;
}
//—————————————————————————————————————void __fastcall TForm1::Button3Click(TObject *Sender)
{
ACMSTREAMHEADER AH; //Вспомогательная структура
SrcFmt.wFormatTag = WAVE_FORMAT_PCM;
SrcFmt.nChannels = (WORD)kanal;
SrcFmt.wBitsPerSample = (WORD)razrad;
SrcFmt.nBlockAlign = (WORD)(kanal * ((razrad+7)/8));
SrcFmt.nSamplesPerSec = diskret;
SrcFmt.nAvgBytesPerSec = SrcFmt.nSamplesPerSec * SrcFmt.nBlockAlign;
DstFmt=SrcFmt;
DstFmt.nSamplesPerSec = diskret1;
DstFmt.nAvgBytesPerSec = DstFmt.nSamplesPerSec * DstFmt.nBlockAlign;
acmStreamOpen (&Stream, NULL,&SrcFmt, &DstFmt, NULL, 0, 0, 0);
AH.cbStruct = sizeof (AH);
AH.fdwStatus = 0;
AH.pbSrc = lpHdr->lpData;
AH.cbSrcLength = lpHdr->dwBytesRecorded;
acmStreamSize(Stream,AH.cbSrcLength,(DWORD *)&DstBufferSize,

Программируем сжатие аудио информации 157
ACM_STREAMSIZEF_SOURCE);
DstBuffer = (BYTE *)GlobalAlloc (GMEM_FIXED, DstBufferSize);
AH.pbDst = DstBuffer;
AH.cbDstLength = DstBufferSize;
acmStreamPrepareHeader (Stream, &AH, 0);
acmStreamConvert (Stream, &AH, ConvertFlags);
acmStreamUnprepareHeader (Stream, &AH, 0);
acmStreamClose(&Stream,NULL);
lpHdr1->lpData = (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
DstBufferSize);
wfx1.wFormatTag = WAVE_FORMAT_PCM;
int nC = wfx1.nChannels = kanal;
int BPS = wfx1.wBitsPerSample = razrad;
int nSPS = wfx1.nSamplesPerSec = diskret1;
int nBA = wfx1.nBlockAlign = nC*BPS/8;
wfx1.nAvgBytesPerSec = nSPS*nBA;
lpHdr1->dwBufferLength = DstBufferSize;
memcpy(lpHdr1->lpData,DstBuffer,DstBufferSize);
}
//—————————————————————————————————————void __fastcall TForm1::Button4Click(TObject *Sender)
{
waveOutOpen(&hWaveOut,WAVE_MAPPER,&wfx,0L,0L,0L);
waveOutPrepareHeader(hWaveOut,lpHdr,sizeof(WAVEHDR));
waveOutWrite(hWaveOut,lpHdr,sizeof(WAVEHDR));
waveOutUnprepareHeader(hWaveOut,lpHdr,sizeof(WAVEHDR));
waveOutClose(hWaveOut);
}
//—————————————————————————————————————void __fastcall TForm1::Button5Click(TObject *Sender)
{
lpHdr1->dwBufferLength = DstBufferSize;
waveOutOpen(&hWaveOut, WAVE_MAPPER,&wfx1,0L,0L,0L);
waveOutPrepareHeader(hWaveOut,lpHdr1,sizeof(WAVEHDR));
waveOutWrite(hWaveOut,lpHdr1,sizeof(WAVEHDR));
waveOutUnprepareHeader(hWaveOut,lpHdr1,sizeof(WAVEHDR));
waveOutClose(&hWaveOut);
}
//—————————————————————————————————————void __fastcall TForm1::Button6Click(TObject *Sender)
{
Close();
}
//—————————————————————————————————————-
Прежде всего заметим, что в программе появились три новые препроцессорные директивы подключения заголовочных файлов vcl.h, mmreg.h è
msacm.h. Первый необходим для реализации возможности вызова функции
вывода сообщений ShowMessage, а два последних содержат необходимые описания для функционирования системы ÀÑÌ. Наряду с переменной diskret,в
которой содержится частота дискретизации для исходного сигнала, описана

158 Глава 4. Запись, обработка и воспроизведение звука
переменная diskret1 для частоты дискретизации в результирующем блоке.
Вместе со структурой буфера входных данных wfx появляется структура для
выходных данных wfx1. Переменная DstBufferSize будет содержать размер буфера результата. Описание HACMSTREAM Stream; задает идентификатор потока преобразования — так называемый ключ потока. Как уже говорилось,
нам понадобятся две структуры типа WAVEFORMATEX для заголовков блоков
исходных и результирующих данных — SrcFmt è DstFmt. Флаг преобразования ConvertFlags нам в данной программе не понадобится, но должен быть
описан. Наконец, *DstBuffer — это указатель на порцию данных результата.
Теперь все новые объекты программы перечислены!
Обратимся к самой главной для нас в данном контексте кнопке
CONVERT с обработчиком Button3Click. Здесь сразу же описана упомянутая
ранее структура ACMSTREAMHEADER AH, которая является промежуточным, временным объектом, необходимым только для преобразования звука.
В этой связи структура сделана локальной в функции-обработчике. Далее заполняются поля структуры источника SrcFmt, и здесь все понятно.
Далее производится открытие потока преобразования acmStreamOpen.
Åñëè ÀÑÌ благополучно найдет и подключит необходимый драйвер (что для
нашей простой конверсии должно неизбежно случиться), то дальше все будет
развиваться в соответствии с описанным выше сценарием. Обратите внимание на то, что в конце тела обработчика выполняется заполнение полей
структуры wfx1 и копирование с помощью функции memcpy конвертированных данных в блок памяти этой структуры.
В функцию-обработчик кнопки STOP REC мы включили анализ списка
ListBox2 — выбор дискретизации для результирующих данных.
Вторая задача, которую мы поручим выполнить менеджеру аудиокомпрессии, заключается в следующем. Звуковой сигнал, подаваемый на вход устройства ввода, должен быть оцифрован в формате ÐÑÌ в соответствии с характеристиками оцифровки, выбираемыми пользователем. Данные временно записываются в блок оперативной памяти и подвергаются сжатию системой ÀÑÌ
в формате ÐÑÌ èëè MPEG Layer-3 с выбираемыми пользователем параметрами. Конвертированный звук должен быть сохранен в файле в первом случае с
расширением .wav, во втором — с расширением .mp3.
Интерфейс приложения теперь может выглядеть примерно так, как это
показано на рис. 4.10.
Относительно предыдущей программы, из которой мы и будем строить
новую, необходимо в первую очередь изменить компоненты формы. Следует
убрать второй список выбора и две кнопки воспроизведения. Сами компоненты удаляются просто клавишей Del. При этом функции-обработчики для
кнопок нужно удалить из текста главного модуля, а ссылки на компоненты и
соответствующие функции убрать из описания класса формы, заданного в заголовочном
файле Unit1.h. На форму следует добавить кнопку SAVE, обработ-
чик которой как раз и выполнит сохранение записи в файле на диске.
Листинг программы представлен ниже. Мы не будем детально рассматри
вать каждую строчку программы, в том числе введение новых переменных,
-

Программируем сжатие аудио информации 159
Ðèñ. 4.10
структур и т. д. Ограничимся только пояснением принципиально новых элементов, появившихся в программе.
//—————————————————————————————————————#include <VCL.h>
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <MMREG.H>
#include "msacm.h"
#include "Unit1.h"
#pragma resource "*.dfm"
#define min(a, b) (((a) < (b)) ? (a) : (b))
TForm1 *Form1;
struct WaveFormat : public tWAVEFORMATEX {
BYTE Ext [128]; // Дополнительные параметры формата
};
int tau,
diskret,diskret1,
razrad=8,
kanal=1;
long buf;
HWAVEIN hWaveIn;
HWAVEOUT hWaveOut;
DWORD DstBufferSize;
HACMSTREAM Stream;
WAVEFORMATEX SrcFmt;
WaveFormat DstFmt;
HMMIO File;
DWORD ConvertFlags;
BYTE *DstBuffer;

160 Глава 4. Запись, обработка и воспроизведение звука
MMCKINFO RiffChunk; // Описатель блока RIFF в файле
MMCKINFO FactChunk; // Описатель блока fact в файле
MMCKINFO DataChunk; // Описатель блока data в файле
UINT ConvertedSamples; // Количество преобразованных отсчетов
ACMSTREAMHEADER AH;
WAVEFORMATEX wfx;
LPWAVEHDR lpHdr = new WAVEHDR;
//————————————————————————————__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ListBox1->ItemIndex = 2;
}
//—————————————————————————————————————-
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(RadioGroup1->ItemIndex == 1) kanal = 2;
if(RadioGroup2->ItemIndex == 1) razrad = 16;
tau = StrToInt(Edit1->Text);
switch (ListBox1->ItemIndex)
{
case 0: diskret = 8000; break;
case 1: diskret = 11025; break;
case 2: diskret = 22050; break;
case 3: diskret = 44100; break;
}
buf = kanal*razrad/8*tau*diskret;
lpHdr->lpData = (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, buf);
wfx.wFormatTag = WAVE_FORMAT_PCM;
int nC = wfx.nChannels = kanal;
int BPS = wfx.wBitsPerSample = razrad;
int nSPS = wfx.nSamplesPerSec = diskret;
int nBA = wfx.nBlockAlign = nC*BPS/8;
wfx.nAvgBytesPerSec = nSPS*nBA;
lpHdr->dwBufferLength = buf;
waveInOpen(&hWaveIn, WAVE_MAPPER,&wfx,0L, 0L, 0L);
waveInPrepareHeader(hWaveIn, lpHdr,sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, lpHdr,sizeof(WAVEHDR));
waveInStart(hWaveIn); // Caioneaai caienu
}
//—————————————————————————————————————-
void __fastcall TForm1::Button2Click(TObject *Sender)
{
waveInReset(hWaveIn);
waveInClose(hWaveIn);
waveInUnprepareHeader(hWaveIn, lpHdr,sizeof(WAVEHDR));
lpHdr->dwBufferLength = lpHdr->dwBytesRecorded;
}
//—————————————————————————————————————-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
