Менеджер медиа-файлов.

Предисловие.

Моё почтение, господа. Не так давно я написал статью, в которой рассказывал о том, как создать класс для проигрывания звука и видео. В течение последних несколько недель, несмотря на сессию, мне приходилось регулярно использовать этот класс. И, честно признаюсь, я нашел его не очень удобным. Спроектирован он был правильно, и если в программе задействовалось немного музыкальных файлов, проблем с их использованием не возникало, но при большом количестве ресурсов управлять ими становилось проблематично. Поэтому этот урок будет посвящен написанию надстройки (объектно-ориентированной, разумеется), упрощающей нам жизнь.

ТЗ.

к началу статьи
Итак, что же мы хотим? Мы хотим класс, который инкапсулировал бы бОльшую часть рутинного кода (инициализация и управление ресурсами), предоставлял удобный интерфейс + некоторые специфические возможности по проигрыванию. Вот вроде бы и все!

Загрузка ресурсов.

к началу статьи
Инициализировать сотни объектов ручками как-то тоскливо, поэтому создадим функцию, принимающую в качестве параметра путь к директории в которой лежит файл со списком ресурсов для загрузки. Вот она:
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. Проигрывание медиа-файлов.
    исходные коды
    © 2004-2005 Savardge.ru
    Hosted by uCoz