|
Менеджер
медиа-файлов.
Предисловие.
Моё почтение, господа. Не так давно я написал статью, в которой
рассказывал о том, как создать класс для проигрывания звука и видео. В
течение последних несколько недель, несмотря на сессию, мне приходилось
регулярно использовать этот класс. И, честно признаюсь, я нашел его не
очень удобным. Спроектирован он был правильно, и если в программе
задействовалось немного музыкальных файлов, проблем с их использованием
не возникало, но при большом количестве ресурсов управлять ими
становилось проблематично. Поэтому этот урок будет посвящен написанию
надстройки (объектно-ориентированной, разумеется), упрощающей нам
жизнь.
ТЗ.
к началу статьи
Итак, что же мы хотим? Мы хотим класс, который инкапсулировал бы
бОльшую часть рутинного кода (инициализация и управление ресурсами),
предоставлял удобный интерфейс + некоторые специфические возможности по
проигрыванию. Вот вроде бы и все!
Загрузка
ресурсов.
к началу статьи
Инициализировать сотни объектов ручками как-то тоскливо, поэтому
создадим функцию, принимающую в качестве параметра путь к директории в
которой лежит файл со списком ресурсов для загрузки. Вот она: |
void cMediaManager::LoadMedias( HWND hWnd , char * path ){ //--------------1-------------- char rel_path[256]; memcpy( rel_path , path , 256 );
strcat( path , "media_enum.log" );
FILE *f_stream = fopen( path , "rt" );
if( !f_stream ){ char error_message[1000]; strcpy( error_message , "Невозможно загрузить описатель медиа файлов : " ); strcat( error_message, path ); MessageBox( 0 , error_message , "Ошибка" , 0 ); exit( 0 ); }
char directive[ 256 ];
for( ; EOF != fscanf( f_stream , "%s" , directive ) ; ){ //--------------2-------------- if( !strcmp( directive , "set_autoselection" ) ){ fscanf( f_stream , "%s" , directive ); RSelection[ GetCursor( directive ) ] = true; } //--------------3-------------- else{ //название sMediaAlias MediaAlias; memcpy( &MediaAlias , directive , 256 ); MediaAliases.AddEnd( MediaAlias );
//читаем название файла fscanf( f_stream , "%s" , directive ); memcpy( path , rel_path , 256 ); strcat( path , directive );
//читаем файл Medias[ Medias.Cursor++ ].LoadFile( hWnd , path );
Medias[ Medias.Cursor - 1 ].SetNotifyWindow( hWnd , WM_DSNOTIFICATION , Medias.Cursor - 1 ); StartingTime.AddEnd( NULL ); RSelection.AddEnd( false ); } } }
|
А вот и сам класс: |
struct sMediaAlias{char Alias[256];};
class cMediaManager{ cArray<sMediaAlias> MediaAliases;
cArray<cMediaPlayer> Medias;
cArray<clock_t> StartingTime;
cArray<bool> RSelection; public: void LoadMedias( HWND , char * ); void HandleEvent( long ); cMediaPlayer &operator[]( int ); cMediaPlayer &operator[]( char* );
int GetCursor( char * );
void RunAppropriateMedia( int ); void RandomSelection( void ); void StopAll( void ); int GetRandomSelCount( void ); };
|
Подробнее об этой
функции: в первом блоке
просто проверяем, существует ли файл (media_enum.log) со списком всех
ресурсов или нет. Если существует, то в блоке 3 читаем и сохраняем в
массиве MediaAliases название ресурса (иногда бывает удобно в качестве
идентификатора использовать название ресурса, а не ID), затем читаем
путь к ресурсу, грузим сам ресурс и указываем, какому окну будет
посылаться сообщения от потоков воспроизведения. При этом в качестве
lParam будет передаваться курсор на этот медиа файл, что бы мы знали,
от какого ресурса мы получили это сообщение. А вот и сам обработчик
событий: |
void cMediaManager::HandleEvent( long lParam ){ if( Medias.Cursor ){ while( SUCCEEDED( Medias[ lParam ].MediaEvent->GetEvent( &evCode, ( LONG_PTR * ) &evParam1 , ( LONG_PTR *) &evParam2 , 0 ) ) ){
Medias[ lParam ].MediaEvent->FreeEventParams( evCode , evParam1 , evParam2 );
if( EC_COMPLETE == evCode ){
Medias[ lParam ].StopFile();
StartingTime[ lParam ] = NULL; } } } }
|
Как вы, наверное,
заметили, ресурсы
должны лежать в той же директории что и media_enum.log. О значении же
блока 2 я расскажу немного позже.
Теперь неплохо бы получить доступ к загруженным файлам: |
cMediaPlayer &cMediaManager::operator[]( char * m_name ){ for( int i( 0 ) ; i < Medias.Cursor ; i++ ){ if( !strcmp( MediaAliases[ i ].Alias , m_name ) ) return( Medias[ i ] ); } char e_message[1000]; strcpy( e_message , "Не найден медиа ресурс. " ); strcat( e_message , m_name ); MessageBox( 0 , e_message , "Ошибка." , 0 ); }
cMediaPlayer &cMediaManager::operator[]( int i ){ if( i < 0 || i >= Medias.Cursor ){ MessageBox( 0 , "Некорректный курсор. cMediaPlayer&cMediaPlayer::operator[]( int i )" , "Ошибка." , 0 ); exit( 0 ); } return( Medias[ i ] ); }
|
Тут вроде бы все
просто – в
первом случае обращаемся к файлу по имени (не самый быстрый способ), а
во втором по курсору. Уже можно проигрывать музыку: |
MediaManager[“some_file”].PlayFile();
|
Дополнительные
возможности.
к началу статьи
К сожалению DirectShow не позволяет одновременно проигрывать разные
участки одного буфера, хотя это иногда необходимо. Я пока не могу
предложить Вам ничего кроме лобового решения: создать несколько буферов
и при необходимости проигрывать их одновременно. Но сразу же возникает
следующая проблема: допустим, вы делаете игру, в которой с какой-то
периодичностью взрываются юниты. А если практически одновременно
взорвется 10 юнитов? Неужели нам надо загружать 10 файлов? Выкрутимся
из затруднения следующим образом: пусть число буферов на один эффект
будет фиксировано (это число зависит от продолжительности файла и его
востребованности). Изначально ни один из буферов не проигрывается.
Возьмем предыдущий пример: как только один из юнитов взрывается, мы
берём первый свободный (то есть который не проигрывается в данный
момент) буфер и начинаем проигрывать его содержимое. Когда взрывается
второй юнит, берём следующий буфер, затем ещё один, затем ещё и так
далее. Когда все свободные буферы закончатся, останавливаем
проигрывание буфера, который был запущен раньше всех, и запускаем его
заново.
Теперь сама реализация:
В классе нам понадобится массив, который будет хранить время начала
воспроизведения для каждого буфера – cArrayStartingTime
и функция, выбирающая необходимый буфер: |
void cMediaManager::RunAppropriateMedia( int i ){ char m_name[256]; memcpy( &m_name , &MediaAliases[ i ] , 256 );
int min_time( StartingTime[ i ] ); int media_cursor( i );
for( int j( 0 ) ; j < Medias.Cursor ; j++ ){ if( !strcmp( m_name , MediaAliases[ j ].Alias ) ){ if( StartingTime[ j ] < min_time ){ min_time = StartingTime[ j ]; media_cursor = j; } } } char Number[100]; //Запускаем найденный файл Medias[ media_cursor ].StopFile(); StartingTime[ media_cursor ] = clock(); Medias[ media_cursor ].PlayFile(); }
|
Алгоритм следующий: по
курсору находим название файла
в цикле
ищем курсор на буфер, чье воспроизведение началось раньше остальных
перезапускаем найденный буфер
Создание
нескольких буферов, можно осуществить без какой-либо доработки кода,
просто в файле media_enum.log нужно несколько раз указать путь к файлу,
например: |
ameno music/ameno.wav ameno music/ameno.wav ameno music/ameno.wav money music/money.mp3 money music/money.mp3
oliver music/oliver.mp3
click sfx/click.wav focus sfx/focus.wav
|
Таким образом, мы
создадим 3 буфера для
ресурса ameno, и 2 буфера для ресурса money.
Shuffle.
к началу статьи
Иногда требуется проигрывать музыкальные композиции в произвольном
порядке (режим автовыбора). Эту функциональность довольно просто
реализовать. Возможно, мы захотим проигрывать не все буферы,
находящиеся в массиве, а только некоторые, поэтому создадим массив
флагов cArray RSelection,
который будет инициализироваться при загрузке ресурсов (блок 2 в
функции cMediaManager::LoadMedias( HWND hWnd , char
* path ) ), следующая композиция для проигрывания будет выбираться из
списка буферов Medias[i] у которых RSelection[i] == true. |
int cMediaManager::GetRandomSelCount( void ){ int ret( 0 ); for( int i( 0 ) ; i < Medias.Cursor ; i++ ){ if( RSelection[ i ] ){ ret++; } } return( ret ); }
|
Эта функция возвращает
число буферов для
которых возможен автовыбор. Сама функция, которая выбирает композицию,
приведена ниже: |
void cMediaManager::RandomSelection( void ){ int rnd_count( GetRandomSelCount() ); int media_cursor( rnd_count * frand() ); char Number[100]; for( int i( 0 ) ; i < Medias.Cursor ; i++ ){ if( RSelection[ i ] ){ if( media_cursor ) media_cursor--; else{ Medias[ i ].PlayFile(); goto end; } } } end:; }
|
frand() –
возвращает
псевдослучайное число из отрезка [0,1].
Вот и все что я хотел Вам сегодня рассказать. Дополнительную
функциональность Вы теперь сможете добавить сами, это не составит Вам
особого труда. Я старался расписывать все как можно подробнее, если же
не смотря на это, возникнут вопросы пищите на
dodonov_a_a(___AT)inbox.ru. До встречи!
к началу статьи
Смежные вопросы:
Урок 1.
Инициализация.
Вершинные буфферы и камера.
Урок
2. Проигрывание медиа-файлов.
исходные коды |
|
|