Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Windows (до Windows 10) Генерация звука с помощью XAudio2 в Windows 8 RSS

Генерация звука с помощью XAudio2 в Windows 8

Текущий рейтинг: 4.5 (проголосовало 2)
 Посетителей: 1393 | Просмотров: 2041 (сегодня 0)  Шрифт: - +
Приложение Windows Store для Windows 8 может легко проигрывать звуковые файлы в формате MP3 или WMA, используя MediaElement: вы просто передаете ему URI или поток звукового файла. Эти приложения также могут обращаться к Play To API для потоковой передачи видео или аудио на внешние устройства.

Но как быть, если вам нужна обработка звука посложнее? Возможно, вы хотели бы модифицировать содержимое аудиофайла или динамически генерировать звуки.

Приложение Windows Store способно выполнять и такие задачи, используя DirectX. Windows 8 поддерживает два компонента DirectX для генерации и обработки звука: Core Audio и XAudio2. По большому счету, оба они являются довольно низкоуровневыми интерфейсами, но Core Audio размещается ниже XAudio2 и больше ориентирован на приложения, требующим более тесного взаимодействия с аппаратным обеспечением.

XAudio2 — потомок DirectSound и библиотеки Xbox XAudio и, по-видимому, ваш лучший выбор для приложений Windows Store, которым нужно делать со звуком какие-то интересные вещи. Хотя XAudio2 предназначен в основном для игр, это не должно останавливать вас от его использования для более серьезных целей, таких как создания музыки или развлечения бизнес-пользователей забавными звуками.

XAudio2 версии 2.8 является встроенным компонентом Windows 8. Как и остальная часть DirectX, программный интерфейс XAudio2 основан на COM. Хотя теоретически возможно обращение к XAudio2 из любого языка программирования, поддерживаемого Windows 8, наиболее естественный и простой язык для XAudio2 — C++. Работа со звуком зачастую требует высокопроизводительного кода, поэтому C++ — хороший выбор и в этом отношении.

Первая программа с XAudio2

Начнем с написания программы, использующей XAudio2 для проигрывания простого пятисекундного звука в ответ на нажатие какой-либо кнопки. Поскольку вы можете быть новичком в программировании для Windows 8 и DirectX, я не буду торопиться в изложении материала.

Я исхожу из того, что у вас установлена версия Visual Studio, подходящая для создания приложений Windows Store. В диалоговом окне New Project выберите Visual C++ and Windows Store слева и Blank App (XAML) в списке доступных шаблонов. Я присвоил проекту имя SimpleAudio, и вы найдете этот проект в пакете исходного кода, который можно скачать для этой статьи.

При сборке исполняемого файла, использующего XAudio2, вам потребуется связать программу с библиотекой импорта xaudio2.lib. Откройте диалоговое окно Properties для проекта, выбрав последний элемент в меню Project или щелкнув правой кнопкой мыши имя проекта в Solution Explorer и выбрав Properties. В левом столбце укажите Configuration Properties, а затем Linker and Input. Щелкните Additional Dependencies (верхний элемент) и небольшую стрелку. Укажите Edit и введите в поле xaudio2.lib.

Кроме того, вам понадобится ссылка на заголовочный файл xaudio2.h, поэтому добавьте следующее выражение в список заранее компилируемых заголовочных файлов в pch.h:

#include <xaudio2.h>

В файле MainPage.xaml я добавил TextBlock для отображения любых ошибок, с которыми может столкнуться программа при работе с XAudio2 API, и Button для проигрывания звука. Все это показано на рис. 1.

Рис. 1. Файл MainPage.xaml для SimpleAudio

<Page x:Class="SimpleAudio.MainPage" ... >
  <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock Name="errorText"
               FontSize="24"
               TextWrapping="Wrap"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />
    <Button Name="submitButton"
            Content="Submit Audio Button"
            Visibility="Collapsed"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Click="OnSubmitButtonClick" />
  </Grid>
</Page>

Основная часть заголовочного файла MainPage.xaml.h показана на рис. 2. Я убрал объявление метода OnNavigatedTo, так как не буду им пользоваться. Обработчик Click для Button объявлен, равно как и четыре поля, подключаемые при использовании программой XAudio2.

Рис. 2. Заголовочный файл MainPage.xaml.h для SimpleAudio

namespace SimpleAudio
{
  public ref class MainPage sealed
  {
  private:
    Microsoft::WRL::ComPtr<IXAudio2> pXAudio2;
    IXAudio2MasteringVoice * pMasteringVoice;
    IXAudio2SourceVoice * pSourceVoice;
    byte soundData[2 * 5 * 44100];
  public:
    MainPage();
  private:
    void OnSubmitButtonClick(Platform::Object^ sender,
      Windows::UI::Xaml::RoutedEventArgs^ args);
  };
}

Любая программа, которой нужен XAudio2, должна создать объект, реализующий интерфейс IXAudio2. (Вскоре вы увидите, как это делается.) IXAudio2 наследует от знаменитого COM-класса IUnknown и поэтому использует его систему учета ссылок, а это означает, что он удаляет свои ресурсы после того, как программа больше не ссылается на них. Класс ComPtr (COM Pointer) в пространстве имен Microsoft::WRL преобразует указатель на COM-объект в смарт-указатель, который отслеживает свои ссылки. Это рекомендуемых подход к работе с COM-объектами в приложении Windows Store.

Любой нетривиальной программе XAudio2 также требуются указатели на объекты, реализующие интерфейсы IXAudio2MasteringVoice и IXaudio2SourceVoice. Во фразеологии XAudio2 «voice» («голос») — объект, который генерирует или модифицирует аудиоданные. Мастеринговый голос (mastering voice) — это концептуально микшер звука, собирающий все индивидуальные голоса и подготавливающий их для оборудования, которое обеспечивает генерацию звука. У вас будет только один такой микшер, но источников голосов, генерирующих отдельные звуки, может быть несколько. (Существуют также способы, позволяющие применять фильтры или эффекты к голосам источников.)

Указатели в IXAudio2MasterVoice и IXAudio2SourceVoice не имеют системы учеты ссылок; сроки их жизни контролируются объектом IXAudio2.

Я также включил большой массив на пять секунд звуковых данных:

byte soundData[5 * 2 * 44100];

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

Как я вычислил размер массива? Хотя XAudio2 поддерживает сжатое аудио, большинство программ, генерирующих звук, будут придерживаться формата, известного под названием «импульсно-кодовая модуляция» (pulse-code modulation, PCM). Звуковые волны в PCM представляются значениями фиксированного размера при фиксированной частоте дискретизации (sampling rate). Для музыки на компакт-дисках используется частота дискретизации 44 100 раз в секунду с 2 байтами на выборку (sample), что в итоге дает для стереозвука 176 400 байтов данных на одну секунду аудио. (При встраивании звуков в приложении рекомендуется применять сжатие. XAudio2 поддерживает ADPCM; кроме того, механизм Media Foundation поддерживает WMA и MP3.)

Для этой программы я выбрал частоту дискретизации 44,1 кГц с двумя байтами на выборку. Таким образом, в C++ каждая выборка считается данными типа short. Пока что я буду использовать монофонический звук, поэтому для секунды аудио потребуется 88 200 байтов. При создании массива этот размер умножается на 5 для пяти секунд аудио.

Создание объектов

Большая часть файла MainPage.xaml.cpp показана на рис. 3. Вся инициализация XAudio2 выполняется в конструкторе MainPage. Он начинает с вызова XAudio2Create для получения указателя на объект, который реализует интерфейс IXAudio2. Это первый шаг в использовании XAudio2. В отличие от некоторых COM-интерфейсов вызов CoCreateInstance не требуется.

Рис. 3. MainPage.xaml.cpp

MainPage::MainPage()
{
  InitializeComponent();
  // Создаем объект IXAudio2
  HRESULT hr = XAudio2Create(&pXAudio2);
  if (FAILED(hr))
  {
    errorText->Text = "XAudio2Create failure: " + hr.ToString();
    return;
  }
  // Создаем мастеринговый голос
  hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice);
  if (FAILED(hr))
  {
    errorText->Text = "CreateMasteringVoice failure: " + hr.ToString();
    return;
  }
  // Создаем голос источника
  WAVEFORMATEX waveformat;
  waveformat.wFormatTag = WAVE_FORMAT_PCM;
  waveformat.nChannels = 1;
  waveformat.nSamplesPerSec = 44100;
  waveformat.nAvgBytesPerSec = 44100 * 2;
  waveformat.nBlockAlign = 2;
  waveformat.wBitsPerSample = 16;
  waveformat.cbSize = 0;
  hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &waveformat);
  if (FAILED(hr))
  {
    errorText->Text = "CreateSourceVoice failure: " + hr.ToString();
    return;
  }
  // Запускаем голос источника
  hr = pSourceVoice->Start();
  if (FAILED(hr))
  {
    errorText->Text = "Start failure: " + hr.ToString();
    return;
  }
  // Заполняем массив звуковыми данными
  for (int index = 0, second = 0; second < 5; second++)
  {
    for (int cycle = 0; cycle < 441; cycle++)
    {
      for (int sample = 0; sample < 100; sample++)
      {
        short value = sample < 50 ? 32767 : -32768;
        soundData[index++] = value & 0xFF;
        soundData[index++] = (value >> 8) & 0xFF;
      }
    }
  }
  // Делаем кнопку видимой
  submitButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
}
void MainPage::OnSubmitButtonClick(Object^ sender, RoutedEventArgs^ args)
{
  // Создаем кнопку для ссылки на байтовый массив
  XAUDIO2_BUFFER buffer = { 0 };
  buffer.AudioBytes = 2 * 5 * 44100;
  buffer.pAudioData = soundData;
  buffer.Flags = XAUDIO2_END_OF_STREAM;
  buffer.PlayBegin = 0;
  buffer.PlayLength = 5 * 44100;
  // Передаем буфер
  HRESULT hr = pSourceVoice->SubmitSourceBuffer(&buffer);
  if (FAILED(hr))
  {
    errorText->Text = "SubmitSourceBuffer failure: " + hr.ToString();
    submitButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    return;
  }
}

После создания объекта IXAudio2 методы CreateMasteringVoice и CreateSourceVoice получают указатели на другие два интерфейса, определенные в заголовочном файле как поля.

Вызов CreateSourceVoice требует структуры WAVEFORMATEX, которая знакома всем, кто работал с аудио в Win32 API. Эта структура определяет природу аудиоданных, которые вы будете использовать для конкретного голоса. (Голоса разных источников могут использовать разные форматы.) В случае PCM по-настоящему важны лишь три параметра: частота дискретизации (44 100 в этом примере), размер каждой выборки (2 байта, или 16 бит) и количество каналов (у меня — 1). Остальные поля неявно вычисляются так: поле nBlockAlign — значение nChannels, умноженное на значение wBitsPerSample, деленное на 8, а поле nAvgBytesPerSec является результатом перемножения nSamplesPerSec и nBlockAlign. Но в данном примере я показал все поля с явными значениями.

Получив объект IXAudio2SourceVoice, вы можете вызвать его метод Start. К этому моменту XAudio2 теоретически воспроизводит звук, но мы пока не предоставили ему никаких аудиоданных для проигрывания. Также доступен метод Stop, и в реальной программе эти два метода будут использоваться для управления тем, когда звук должен проигрываться, а когда — нет.

Все эти четыре вызова не так просты, как выглядят в этом коде! Все они имеют дополнительные аргументы, но определены удобные значения по умолчанию, и пока я просто предпочел согласиться с этими значениями.

Практически все вызовы функций и методов в DirectX возвращают HRESULT-значения, указывающие на успешное выполнение или ошибку. Эти ошибки обрабатываются по разным стратегиям. Я выбрал простое отображение кода ошибки с помощью TextBlock, определенного в XAML-файле, и прекращение дальнейшей обработки.

Аудиоданные PCM

Конструктор завершают свою работу заполнением массива soundData аудиоданными, но этот массив не передается IXAudio2SourceVoice, пока не будет нажата кнопка.

Звук — это колебания, и люди чувствительны к колебаниям в воздухе примерно в диапазоне от 20 Гц (или циклов в секунду) до 20 000 Гц. Нота до в средней октаве (middle C) на пианино приблизительно соответствует 261,6 Гц.

Допустим, вы работаете с частотой дискретизации 44 100 Гц и 16-разрядными выборками и хотите генерировать аудиоданные для звуковой волны с частотой 4410 Гц, что как раз на одну ноту выше последней клавиши на пианино в самой высокой октаве. Каждый цикл такой звуковой волны требует 10 выборок 16-битных значений со знаком. Эти 10 значений будут повторяться 4410 раз на каждую секунду звука.

Рендеринг звуковой волны с частотой 441 Гц (очень близкая к 440 Гц, соответствующим ноте ля над нотой до средней октавы, используемой как настроечный стандарт) осуществляется 100 выборками. Этот цикл будет повторяться 441 раз на каждую секунду звука.

Поскольку PCM включает постоянную частоту дискретизации, низкочастотные звуки выбираются и подвергаются рендерингу с гораздо более высоким разрешением, чем высокочастотные звуки. Не является ли это проблемой? Не вносится ли в волновой сигнал частотой 4410 Гц, рендеринг которого осуществляется всего 10 выборками, значительные искажения по сравнению с волновым сигналом на 441 Гц?

Оказывается, что искажение за счет квантования (quantization distortion) в PCM происходит при частотах выше половины частоты дискретизации. (Это частота Найквиста [Nyquist frequency], названная так в честь Гарри Найквиста [Harry Nyquist], инженера из Bell Labs.) Одна из причин, по которой для музыкальных компакт-дисков была выбрана частота дискретизации 44,1 кГц, заключается в том, что частота Найквиста в этом случае составляет 22 050 Гц, а человеческое ухо слышит максимум около 20 000 Гц. Иначе говоря, при частоте дискретизации 44,1 кГц искажение за счет квантования не слышны людям.

Программа SimpleAudio генерирует алгоритмически простой волновой сигнал — прямоугольную волну с частотой 441 Гц. На один цикл приходится 100 выборок. В каждом цикле первые 50 — это максимальные положительные значения (32 767 при работе с типом short), а следующие 50 — максимальные отрицательные значения (–32 768). Заметьте, что эти short-значения должны храниться в байтовом массиве, где первым всегда является младший байт:

soundData[index + 0] = value & 0xFF;
soundData[index + 1] = (value >> 8) & 0xFF;

До сих пор ничего реально не воспроизводилось. Это происходит в обработчике Click для Button. Для ссылки на байтовый массив со счетчиком байтов и длительностью звучания, указываемой как количество выборок, используется структура XAUDIO2_BUFFER. Этот буфер передается методу SubmitSourceBuffer объекта IXAudio2SourceVoice. Если метод Start уже вызван (как в данном примере), воспроизведение звука начинается немедленно.

Подозреваю, что я забыл упомянуть о том, что звук проигрывается асинхронно. Вызов SubmitSourceBuffer может вернуть управление сразу же, пока выделенный поток занимается пересылкой данных звуковому оборудованию. XAUDIO2_BUFFER, передаваемый в SubmitSourceBuffer, может быть удален после вызова — как это делается в моей программе, когда обработчик Click завершает работу и локальная переменная выходит из области видимости, — но сам байтовый массив должен остаться в доступной памяти. Ваша программа может манипулировать этими байтами, пока воспроизводится звук. Однако есть способы куда лучше (включая методы обратного вызова), позволяющие вашей программе динамически генерировать звуковые данные.

Без обратного вызова для определения того, когда проигрывание звука закончилось, этой программе приходится удерживать в памяти массив soundData на все время ее выполнения.

Вы можете нажать кнопку несколько раз, и каждый вызов в конечном счете будет ставить в очередь другой буфер для проигрывания после того, как закончится воспроизведение предыдущего буфера. Если программа переключается в фоновое выполнение, звук заглушается, но воспроизведение не останавливается — просто его не слышно. Другими словами, если вы щелкаете кнопку и переводите программу в фоновое выполнение по крайней мере на пять секунд, то ничего не услышите, пока окно программы не станет активным.

Характеристики звука

Большая часть звуков, которые мы слышим в повседневной жизни, поступает одновременно из множества различных источников, и поэтому звуковое окружение довольно сложное. Однако в некоторых случаях, в частности при музыкальных звуках, индивидуальные тона можно определить всего несколькими характеристиками:

  • амплитудой, интерпретируемой нашими органами чувств как громкость;
  • частотой, интерпретируемой как высота звука (pitch);
  • пространством (space), которое можно имитировать при воспроизведении аудио с помощью нескольких колонок;
  • тембром, который связан со смесью обертонов в звуке и представляет воспринимаемую разницу между звучанием, например, духового инструмента и пианино.

Проект SoundCharacteristics демонстрирует эти четыре характеристики по отдельности. Как и в проекте SimpleAudio, в нем используется частота дискретизации 44,1 кГц и 16-разрядные выборки, но звук генерируется в стерео. Для двухканального звука данные должны перекрываться: 16-разрядная выборка для левого канала должна сменяться 16-разрядной выборкой для правого канала и т. д.

В заголовочном файле MainPage.xaml.h для SoundCharacteristics определено несколько констант:

static const int sampleRate = 44100;
static const int seconds = 5;
static const int channels = 2;
static const int samples = seconds * sampleRate;

В нем также определены четыре массива для аудиоданных, но эти массивы имеют тип short, а не byte:

short volumeSoundData[samples * channels];
short pitchSoundData[samples * channels];
short spaceSoundData[samples * channels];
short timbreSoundData[samples * channels];

Использование массивов типа short упрощает инициализацию, так как 16-разрядные звуковые данные не приходится разбивать на две части. Простое преобразование позволяет ссылаться на массив из XAUDIO2_BUFFER при передаче звуковых данных. Эти массивы содержат в два раза больше байтов, чем массив в SimpleAudio, поскольку теперь я использую стереозвук.

Все четыре массива инициализируются в конструкторе MainPage. Для демонстрации громкости по-прежнему используется прямоугольная волна с частотой 441 Гц, но воспроизведение начинается с нулевой громкости, потом в течение двух секунд постепенно нарастает, а затем в течение двух секунд уменьшается. Код для инициализации volumeSoundData показан на рис. 4.

Рис. 4. Звуковые данные, громкость которых изменяется для демонстрации в SoundCharacteristics

for (int index = 0, sample = 0; sample < samples; sample++)
{
  double t = 1;
  if (sample < 2 * samples / 5)
    t = sample / (2.0 * samples / 5);
  else if (sample > 3 * samples / 5)
    t = (samples - sample) / (2.0 * samples / 5);
  double amplitude = pow(2, 15 * t) - 1;
  short waveform = sample % 100 < 50 ? 1 : -1;
  short value = short(amplitude * waveform);
  volumeSoundData[index++] = value;
  volumeSoundData[index++] = value;
}

Человеческое ухо воспринимает громкость по логарифмической зависимости: каждое удвоение амплитуды звуковой волны эквивалентно увеличению громкости на 6 дБ. (16-разрядная амплитуда, используемая для CD-аудио, имеет динамический диапазон 96 дБ.) Код на рис. 4, изменяющий громкость, сначала вычисляет значение t, которое линейно увеличивается от 0 до 1, а потом уменьшается обратно до 0. Значение переменной amplitude вычисляется функцией pow и варьируется в диапазоне от 0 до 32 767. Это значение умножается на форму прямоугольной волны, которая имеет значения 1 и –1. Результат добавляется в массив дважды: сначала для левого канала, потом для правого.

Восприятие частоты человеком тоже подчиняется логарифмической зависимости. В большей части музыки в мире высота определяется интервалом октавы, где частота удваивается. Первые две ноты припева в песне «Somewhere over the Rainbow» имеют октавный скачок (octave leap) независимо от того, кто исполняет ее — бас или сопрано. На рис. 5 показан код, который варьирует высоту в диапазоне двух октав: от 220 до 880 Гц в зависимости от значения t, которое (как и в примере с громкостью) увеличивается от 0 до 1, а затем уменьшается обратно до 0.

Рис. 5. Звуковые данные, частота которых изменяется для демонстрации в SoundCharacteristics

double angle = 0;
for (int index = 0, sample = 0; sample < samples; sample++)
{
  double t = 1;
  if (sample < 2 * samples / 5)
    t = sample / (2.0 * samples / 5);
  else if (sample > 3 * samples / 5)
    t = (samples - sample) / (2.0 * samples / 5);
  double frequency = 220 * pow(2, 2 * t);
  double angleIncrement = 360 * frequency / waveformat.nSamplesPerSec;
  angle += angleIncrement;
  while (angle > 360)
    angle -= 360;
  short value = angle < 180 ? 32767 : -32767;
  pitchSoundData[index++] = value;
  pitchSoundData[index++] = value;
}

В первых примерах я выбирал частоту 441 Гц, потому что частота дискретизации 44 100 Гц без остатка делится на нее. В общем случае частота дискретизации не является кратной частоте, а значит, не может быть целого количества выборок на каждый цикл. Вместо этого моя программа поддерживает переменную angleIncrement с плавающей точкой, значение которой пропорционально частоте и которая используется для увеличения значения angle, варьируемого от 0 до 360. На основе значения angle можно потом сконструировать форму звуковой волны.

Демонстрация пространства перемещает звук от центра к левому каналу, затем к правому и, наконец, обратно в центр. В демонстрации тембра форма звуковой волны начинается с синусоидальной кривой при частоте 441 Гц. Синусоидальная кривая является математическим представлением наиболее фундаментального типа колебаний — это решение дифференциального уравнения, где сила обратно пропорциональна амплитуде смещения (displacement). Все остальные периодические волновые колебания содержат гармоники, которые также являются синусоидальными волнами, но с частотами, кратными базовой частоте. В демонстрации тембра форма звуковой волны плавно меняется с синусоидальной волны на треугольную волну, потом на прямоугольную и, наконец, на пилообразную, параллельно увеличивая количество гармоник в звуке.

Общая картина

Хотя я продемонстрировал лишь то, как можно управлять громкостью, высотой, объемностью и тембром, генерируя звуковые данные для единственного объекта IXAudio2SourceVoice, этот объект сам включает методы для изменения громкости и позиционирования звука в пространстве, а также его частоты. (Механизм создания трехмерного пространства тоже поддерживается.) Хотя можно генерировать составные звуковые данные, в которых набор индивидуальных тонов комбинируется с голосом одного источника, вы вправе создать несколько объектов IXAudio2SourceVoice и воспроизводить их все вместе через один и тот же мастеринговый голос.

Кроме того, XAudio2 определяет IXAudioSubmixVoice, позволяющий определять фильтры и другие эффекты, например реверберацию или эхо. Фильтры дают возможность динамически изменять тембр существующих тонов и тем самым способны внести значительный вклад в создание интересных и реалистичных музыкальных звуков.

Самое важное усовершенствование, выходящее за рамки показанного мной в этих двух программах, по-видимому, потребует работы с функциями обратного вызова в XAudio2. Вместо создания и инициализации больших порций звуковых данных, как это делается в моих двух программах, имеет куда больший смысл динамически генерировать звуковые данные по мере их проигрывания.

Автор: Чарльз Петцольд  •  Иcточник: MSDN Magazine  •  Опубликована: 21.05.2013
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Windows 8, XAudio2.


Оценить статью:
Вверх
Комментарии посетителей
08.04.2017/00:51  golper

А исходник есть?
Комментарии отключены. С вопросами по статьям обращайтесь в форум.