Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
Программы записи и воспроизведения звука 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;
}
//—————————————————————————————————————-