netlib.narod.ru< Назад | Оглавление | Далее >

Как воспроизвести файл MP3

Хотите ли вы, чтобы в вашей игре было реализовано воспроизведение файлов MP3? Если да, этот раздел для вас. К сожалению, воспроизведение файлов MP3 требует полностью нового набора интерфейсов и функций. Разве не удивляет такой быстрый переход от радости к сожалениям? Такова жизнь, мой друг.

На тот случай, если последние несколько лет вы провели на необитаемом острове, сообщу, что MP3 — это формат для хранения звуковых данных с высокой степенью сжатия. Большинство людей использует его для песен, но он также подходит и для речи и звуковых эффектов. Единственный недостаток формата MP3 — большая, по сравнению с другими форматами, нагрузка на процессор при воспроизведении. Учитывая производительность современных процессоров, эта особенность не представляет большой проблемы, но ее все же следует иметь в виду.

В сопроводительные файлы к этой книге включен проект с названием DShow_PlayMP3. На рис. 7.4 показано окно, выводимое этой программой.


Рис. 7.4. Окно программы DShow_PlayMP3

Рис. 7.4. Окно программы DShow_PlayMP3


Согласен, в изображенном на рис. 7.4 окне нет ничего особенного. Но для этого есть причина: программа создана для воспроизведения файлов MP3, а не для показа вращающихся трехмерных кубов!

Теперь загрузите проект DShow_PlayMP3, чтобы иметь возможность идти дальше. Я рекомендую вам скомпилировать и запустить программу, чтобы услышать результат ее работы. Если вы ничего не услышали, проверьте строку, в которой указано имя файла c:\dxsdk\samples\media\track.mp3. Если в указанном каталоге у вас нет MP3-файла, скорректируйте путь, чтобы он указывал на любой существующий в вашей системе файл MP3. Несколько пригодных для воспроизведения файлов входят в DirectX SDK.

DirectShow

Первая вещь на которой следует остановиться — имя проекта. В отличие от первых двух проектов из этой главы, имя данного проекта начинается с префикса DShow. Я сделал это потому, что данная программа использует DirectShow а не DirectMusic. DirectShow представляет собой отдельный интерфейс DirectX предназначенный для работы с потоковой аудиовизуальной информацией в Windows. Он может применяться для воспроизведения различных форматов, в том числе AVI, MPEG, MP3 и даже WAV. Как видите, вы можете воспроизводить не только звук, но и видео, а также комбинировать оба этих способа. Это действительно замечательная возможность, открывающая дорогу к воспроизведению видеофрагментов в начале вашей игры и между уровнями.

Проект DShow_PlayMP3

Программа содержит несколько файлов с исходным кодом: main.cpp, main.h и DXUtil.cpp. Все исходные файлы, за исключением DXUtil.cpp, являются уникальными для данного проекта. Кроме того, в проекте используются следующие библиотеки: dxguid.lib, winmm.lib и Strmiids.lib. Файл Strmiids.lib необходим для работы с DirectShow.

Заголовочный файл Main.h

Заголовочный файл main.h содержит обычный набор объявлений глобальных переменных и директив включения файлов, необходимых для примера. Вот как выглядит код секции с директивами включения файлов:

#include <windows.h>
#include <stdio.h>
#include <D3DX9.h>
#include <dxutil.h>
#include <dshow.h>

Новым в этом блоке является включение файла dshow.h. Он необходим для вызова интерфейсов и функций DirectShow. Если вы планируете использовать функциональность DirectShow, убедитесь, что этот файл есть в списке включаемых.

В следующем блоке кода находятся прототипы функций. Вот как он выглядит:

LRESULT WINAPI fnMessageProcessor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void vCleanup(void);
bool bPlayTitleMusic(void);
void vStopTitleMusic(void);
void vCheckMusicStatus(void);

Функция fnMessageProcessor() — это обычный обработчик сообщений Windows. Здесь нет ничего нового — все та же старая чепуха.

Функция vCleanup() вызывается перед выходом из программы. Она выполняет освобождение интерфейсов.

Функция bPlayTitleMusic() вызывается один раз в начале программы. Она инициализирует DirectShow, загружает файл MP3 и начинает его воспроизведение.

Функция vStopTitleMusic() останавливает воспроизведение музыки перед завершением работы программы.

Функция vCheckMusicStatus() проверяет не завершилось ли воспроизведенеи музыкального сегмента. Если да, воспроизведение музыки повторяется с начала.

Далее в заголовочном файле расположены глобальные переменные. Я создаю несколько указателей на интерфейсы и одну логическую переменную, как показано ниже:

bool            g_bBackgroundMusicActive = 0;
IGraphBuilder   *g_pGraph;
IMediaControl   *g_pMediaControl;
IMediaEvent     *g_pEvent;
IMediaSeeking   *g_pSeeking;

Переменная g_bBackgroundMusicActive используется для отслеживания состояния музыки. Если музыка воспроизводится, ее значение равно 1. Если нет, значение равно 0.

Переменная g_pGraph является указателем на интерфейс IGraphBuilder. Эй, эй, что это за граф?

Граф фильтров

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

Например, граф фильтров может читать файл MP3 и вормировать звук для его вывода аудиооборудованием. Эти действия показаны на рис. 7.5.


Рис. 7.5. Граф фильтров MP3

Рис. 7.5. Граф фильтров MP3


Как видно на рис. 7.5, граф фильтров читает данные из файла MP3, декодирует их, а затем отправляет аудиоаппаратуре для воспроизведения. Рабочей лошадкой индустрии фильтров в DirectShow является интерфейс IGraphBuilder. В таблице 7.6 перечислены входящие в этот интерфейс функции.

Таблица 7.6. Методы интерфейса IGraphBuilder

Метод Описание
Abort Сообщает графу о необходимости прекратить текущую операцию.
AddSourceFilter Добавляет фильтр источника.
Connect Соединяет два контакта.
Render Добавляет фильтр к выходному контакту.
RenderFile Загружает файл для воспроизведения. Я использую этот метод в своем примере для загрузки MP3-файла.
SetLogFile Устанавливает обработчик для файла журналирования выходной информации.
ShouldOperationContinue Сообщает должна ли продолжаться операция. Это очень странная функция, которую вам никогда не придется вызывать.

Управление аудиовизуальным потоком

В следующей строке кода заголовочного файла я создаю указатель на интерфейс IMediaControl с именем g_pMediaControl. Интерфейс управления аудиовизуальным потоком предназначен для контроля проходящих через граф фильтров данных. Этот интерфейс позволяет запустить, закончить и даже временно приостановить прохождение данных через граф. Вы можете представлять его как пульт дистанционного упроавления видеомагнитофона.

В рассматриваемом примере программы я использую интерфейс управления аудиовизуальным потоком для запуска, прекращения и перезапуска музыки. Функции интерфейса перечислены в таблице 7.7.

Таблица 7.7. Методы интерфейса IMediaControl

Метод Описание
GetState Возвращает состояние графа.
Pause Приостанавливает воспроизводимый в данный момент аудиовизуальный поток.
Run Запускает аудиовизуальный поток. Это аналог кнопки Play на пульте дистанционного управления видеомагнитофона.
Stop Завершает воспроизведение аудиовизуального потока.
StopWhenReady Более мягкая остановка.

События аудиовизуального потока

Следующим в заголовочном файле расположен указатель на интерфейс IMediaEvent. Этот тип интерфейса используется для коммуникации с графом фильтров. Он будет информировать вас о текущем состоянии воспроизводимого аудиовизуального потока. В рассматриваемом примере программы я использую данный интерфейс, чтобы получить сообщение о завершении воспроизведения музыки. Методы интерфейса перечислены в таблице 7.8.

Таблица 7.8. Методы интерфейса IMediaEvent

Метод Описание
CancelDefaultHandling Отменяет установленную по умолчанию обработку события фильтром.
FreeEventParams Освобождает связанные с параметром ресурсы.
GetEvent Возвращает следующее событие из очереди.
GetEventHandle Возвращает дескриптор следующего сообщения в очереди.
RestoreDefaultHandling Восстанавливает обработчик по умолчанию.
WaitForCompletion Ожидает пока граф фильтров не завершит воспроизведение аудиовизуального потока. Я использую эту функцию в примере программы чтобы проверить завершено ли воспроизведение музыки.

Позиционирование аудиовизуального потока

Следующий интерфейс называется IMediaSeeking. Как следует из его названия, он предназначен для установки позиции в аудиовизуальном потоке. Кроме того, он позволяет задать темп воспроизведения аудиовизуального потока. В рассматриваемом примере программы я использую этот интерфейс чтобы осуществить перемотку аудиовизуального потока к его началу, когда воспроизведение музыки заканчивается. Так же я использую его для задания темпа воспроизведения. Методы данного интерфейса перечислены в таблице 7.9.

Таблица 7.8. Методы интерфейса IMediaSeeking

Метод Описание
CheckCapabilities Проверяет, обладает ли поток указанными возможностями.
ConvertTimeFormat Преобразует из одного формата в другой.
GetAvailable Возвращает доступный диапазон значений времени для позиционирования.
GetCapabilities Возвращает возможности аудиовизуального потока.
GetCurrentPosition Возвращает текущую позицию в потоке.
GetDuration Возвращает длину потока.
GetPositions Возвращает текущую и конечную позиции.
GetPreroll Возвращает размер аудиовизуального потока, расположенного перед начальной позицией.
GetRate Возвращает темп воспроизведения.
GetStopPosition Возвращает конечную позицию. Она сообщает вам, когда воспроизведение потока будет завершено.
GetTimeFormat Возвращает используемый в данный момент формат времени.
IsFormatSupported Проверяет поддерживается ли указанный формат времени.
IsUsingTimeFormat Проверяет используется ли в данный момент указанный формат времени.
QueryPreferredFormat Возвращает предпочтительный формат времени.
SetPositions Устанавливает текущую и завершающую позиции.
SetRate Устанавливает темп воспроизведения.
SetTimeFormat Устанавливает формат времени.


В описываемой программе я использую функции SetRate() и SetPositions().

Файл программы Main.cpp

Основной код программы располагается в файле main.cpp. Загрузите его сейчас и следуйте дальше. Найдите в коде функцию WinMain() и обратите внимание на следующий фрагмент:

// Воспроизведение музыки
bRet = bPlayTitleMusic();
if(bRet == 0) {
   MessageBox(hWnd, "Initialization Failure",
      "Failed to initialize DirectShow",
      MB_ICONEXCLAMATION | MB_OK);
   // Сбой в программе, выход
   exit(1);
}

В этом блоке кода вызывается функция bPlayTitleMusic(), являющаяся локальной для моей программы. Она отвечает за инициализацию DirectShow и воспроизведение файла MP3 из каталога с звуковыми файлами DirectX SDK. Давайте перейдем к этой функции.

Функция bPlayTitleMusic()

Эта функция в рассматриваемой программе выполняет большую часть работы. Она инициализирует COM, создает интерфейсы, загружает музыку, устанавливает темп воспроизведения и начинает проигрывание музыки. Вот как выглядит ее код:

bool bPlayTitleMusic(void)
{
   HRESULT hr;

   // Инициализация COM
   CoInitialize(NULL);
   // Создание графа
   CoCreateInstance(CLSID_FilterGraph,
      NULL,
      CLSCTX_INPROC_SERVER,
      IID_IGraphBuilder,
      (void **)&g_pGraph);
   // Запрос интерфейсов объекта
   g_pGraph->QueryInterface(
      IID_IMediaControl, (void **)&g_pMediaControl);
   g_pGraph->QueryInterface(
      IID_IMediaEvent, (void **)&g_pEvent);
   g_pGraph->QueryInterface(
      IID_IMediaSeeking, (void **)&g_pSeeking);

   // Загрузка песни (вставьте имя своего файла)
   hr = g_pGraph->RenderFile(
      L"c:\\dxsdk\\samples\\media\\track3.mp3", NULL);
   if(hr != S_OK) {
      return(0);
   }
   // Установка темпа воспроизведения
   g_pSeeking->SetRate(1);
   // Воспроизведение музыки
   g_pMediaControl->Run();
   // Установка флага воспроизведения
   g_bBackgroundMusicActive = 1;
   return(1);
}
Инициализация DirectShow

Первая вещь, которую делает функция, — инициализация COM. Это необходимый этап, поскольку DirectShow использует COM-интерфейсы.

Следующий шаг к небесам DirectShow — создание объекта графа. Я выполняю это с помощью вызова функции CoCreateInstance(). Интерфейс IGraphBuilder использует CLSID CLSID_FilterGraph и идентификатор интерфейса IID_IGraphBuilder. Указатель на интерфейс сохраняется в глобальной переменной с именем g_pGraph.

Теперь, когда у вас есть интерфейс построителя графов, вы можете создать другие интерфейсы, отправив запрос построителю графов. Это делается с помощью функции IGraphBuilder::QueryInterface(). Мы создаем три интерфейса: IID_IMediaControl, IID_IMediaEvent и IID_IMediaSeeking. После создания указатель на каждый из интерфейсов сохраняется в глобальных переменных, о которых я упоминал ранее.

К данному моменту вы инициализировали COM, создали объект графа и необходимые для программы вспомогательные интерфейсы. Это основные действия инициализации, необходимые для воспроизведения файлов MP3. Осталось только загрузить файл с музыкой, установить темп воспроизведения и начать воспроизведение. Перед тем, как идти дальше, взгляните на рис. 7.6.


Рис. 7.6. Этапы инициализации DirectShow

Рис. 7.6. Этапы инициализации DirectShow


На рис. 7.6 показана взаимосвязь между интерфейсом управления аудиовизуальным потоком, интерфейсом событий аудиовизуального потока, интерфейсом позиционирования аудиовизуального потока и интерфейсом графа фильтров. Также видна связь между функциями воспроизведения и интерфейсом управления аудиовизуальным потоком и между функцией установки темпа и интерфейсом позиционирования аудиовизуального потока. Позднее вы увидите как в эту большую картину вписывается интерфейс событий аудиовизуального потока.

Загрузка музыкального файла

Функция IGraphBuilder::RenderFile() выполняет всю работу, необходимую для загрузки файла. Она получает два параметра: первый содержит строку с именем файла, а второй не используется. Как видно из кода в рассматриваемой программе я загружаю файл c:\dxsdk\samples\media\track3.mp3. Если у вас DirectX SDK установлен в другом каталоге, убедитесь что путь соответствующим образом скорректирован. Вы можете скорректировать имя файла, чтобы воспроизводить любую из имеющихся на вашем компьютере песен в формате MP3. Лично я указал песню Amish Paradise, которую исполняет Weird Al Yankovic. Поскольку у меня нет прав на распространение этой песни, пришлось указать файл, который, скорее всего, будет на диске вашего компьютера!

Если загрузка файла прошла успешно, функция возвращает значение S_OK. Если же при загрузке произошел сбой, вам может понадобиться консультация с документацией DirectX SDK для получения информации о возвращаемом коде ошибки.

Установка темпа воспроизведения

Давайте двигаться дальше. Следующий фрагмент кода задает темп песни. Темп музыки определяет насколько быстро (или медленно) она воспроизводится. Данный параметр может использоваться для того, чтобы голос актера звучал похоже на белку или на Дарта Вейдера. Все это делает функция IMediaSeeking::SetRate(). Она получает единственный параметр — темп воспроизведения. Если вы хотите, чтобы для воспроизведения песни использовался ее темп по умолчанию, укажите здесь значение 1. Чтобы услышать, как артист поет с удвоенной скоростью, укажите значение 2. Кроме того, вы можете использовать различные промежуточные значения. Например, я люблю воспроизводить музыку группы Metallica с темпом 1.25, чтобы получить чистый скоростной металл. Вы почти можете видеть, как руки Ларса дымятся от этого!

Воспроизведение музыки

После всей выполненной работы воспроизведение MP3 осуществляется исключительно просто. Достаточно лишь вызвать функцию IMediaControl::Run(). Если функция возвращает S_OK, вы знаете, что воспроизведение успешно началось.

Перехват фоновых событий

Теперь, когда все необходимые части проинициализированы и запущены, код присваивает флагу g_bBackgroundMusicActive значение 1. Благодаря этому цикл сообщений функции WinMain() узнает, что надо проверять состояние музыки. Вернитесь назад к функции WinMain() и взгляните на следующий фрагмент кода:

if(g_bBackgroundMusicActive) {
   vCheckMusicStatus();
}

Эй, я не говорил, что будет много кода! Так или иначе, главный цикл проверяет состояние музыки вызывая мою функцию vCheckMusicStatus().

Функция vCheckMusicStatus()

Функция vCheckMusicStatus() проверяет завершено ли фоновое воспроизведение музыки. Если да, музыка перематывается к началу и воспроизведение запускается по новой. Вот как выглядит код функции:

void vCheckMusicStatus(void)
{
   long      evCode;

   // Проверка кода события
   g_pEvent->WaitForCompletion(0, &evCode);
   // Если музыка закончилась, запустить ее заново
   if(evCode == EC_COMPLETE) {
      // Устанавливаем начальную позицию в 0
      LONGLONG lStartPos = 0;
      // Останавливаем музыку
      g_pMediaControl->Stop();
      // Устанавливаем позиции
      g_pSeeking->SetPositions(
         &lStartPos,
         AM_SEEKING_AbsolutePositioning,
         NULL, AM_SEEKING_NoPositioning);
      // Запускаем музыку
      g_pMediaControl->Run();
   }
}
Проверка кода события

Пришло время использовать старый верный интерфейс IMediaEvent. Этот интерфейс позволяет увидеть состояние музыки. Я вызываю функцию IMediaEvent::WaitForCompletion() чтобы получить самый верхний код события.

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

Если код события равен EC_COMPLETE, я знаю, что воспроизведение песни закончилось. Это явная подсказка, что пора перемотать песню к началу и снова запустить ее воспроизведение. Если возвращен код EC_ERRORABORT или EC_USERABORT, я знаю, что что-то пошло наперекосяк и воспроизведение музыки прекращено.

Остановка музыки

Следующий фрагмент кода подразумевая, что песня подошла к концу, останавливает музыку, перематывает ее к началу и заново начинает воспроизведение. Остановка музыки осуществляется вызовом функции IMediaControl::Stop(). Параметров у функции нет.

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

Перемотка музыки

Теперь, когда музыка остановлена, вам необходимо перемотать ее к началу. Конечно же никакой реальной перемотки не выполняется; вы просто снова устанавливаете указатель позиции на начало песни. Это выполняет функция IMediaSeeking::SetPositions(). Вот как выглядит ее прототип:

HRESULT SetPositions(
   LONGLONG *pCurrent,
   DWORD dwCurrentFlags,
   LONGLONG *pStop,
   DWORD dwStopFlags
);

Первый параметр является адресом переменной, содержащей устанавливаемую позицию песни. Поскольку я хочу перемотать песню к началу, значение этой переменной равно 0.

Следующий параметр, dwCurrentFlags, является комбинацией битовых флагов, относящихся к устанавливаемой позиции. Существует два типа флагов: флаги позиционирования и модификаторы. Здесь я использую флаг AM_SEEKING_AbsolutePositioning, чтобы сообщить системе, что позиция 0 является абсолютной, а не относительной. Названия трех других флагов описывают их назначение: AM_SEEKING_NoPositioning, AM_SEEKING_RelativePositioning и AM_SEEKING_IncrementalPositioning.

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

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

Запуск музыки

Теперь, после того, как песня остановлена и перемотана к началу, я снова запускаю ее воспроизведение с помощью испытанной функции запуска, которой я уже пользовался ранее при инициализации.

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


Рис. 7.7. Поток исполнения программы воспроизведения MP3

Рис. 7.7. Поток исполнения программы воспроизведения MP3


На рисунке видно как система инициализирует DirectShow, загружает песню и начинает ее воспроизведение, проверяет не закончилась ли песня, перематывает ее и запускает воспроизведение снова. На этом мы завершаем обсуждение воспроизведения файлов MP3.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz