Мультиплатформенный аудио плеер на C++ и OpenAL
Привет Хабр!
Так сложилось, что большую часть жизни я пользовался Windows и привык воспроизводить аудио файлы с помощью Winamp. Он очень удобно интегрируется с командной строкой — запустил любой аудио файл и готово. После перехода на Linux и OS X (в основном по работе, но Mac использую и дома вместе с виндой) возникла острая необходимость найти альтернативу. Перепробывал большое количестко крафических плееров. Основная их проблема — это отсутствие нормальной интеграции с командной строкой и часто поддержка только одной из платформ: либо Linux, либо OS X. С консольными плеерами ситуация получше: mpg123 и mpg321 практически идеально делают именно то, что надо. Вот только появилось одно большое «но». Они не умеют играть .ogg и трекерную музыку (.it, .mod, .xm, .s3m и прочие), которой тоже накопилось достаточно и расставаться с ней совершенно не хотелось.
Дело в том, что за свою программистскую карьеру мне пришлось написать пару мультиплатформных аудио систем для игровых движков: для Linderdaum Engine и для Blippar и ещу одну небольшую для вот этой книжки. Почему бы не применить накопленный опыт, чтобы самому написать проигрыватель? Требования для плеера получились вот такими:
- работать на Windows, Linux и OS X;
- проигрывать MP3, Vorbis, WAV и максимальное разумное кол-во модульных аудио форматов;
- удобная интеграция с командной строкой;
- использовать максимум сторонних библиотех, чтобы не превращать проект в долгострой;
На самом деле первая версия, которая заменила mpg123, была написана за 3 дня. А версия, которая могла проиграть всю музыкальную коллекцию из ~12 тысяч файлов потребовала ровно месяц. В качестве бэк-энда для вывода звука было решено использовать OpenAL (OpenAL Soft на Linux и официальная поддержка на OS X). Для декодинга звуковых форматов используются libogg, libvorbis, minimp3, libmodplug и id3v2lib. Написание плеера «немного» отличается от написания аудиосистемы для игры (кроме того, что необходим только один единственный источник звука без всякого 3D позиционирования и эффектов). По сути главное отличие в том, что звуковые форматы на воле это совсем не то же самое, что звуковые ассеты для игрового проекта. Могут быть битые файлы, странные тэги, нестандартные добавки в конце файла, необычные .mp3 в которых сэмплинг рейт меняется от фрейма к фрейму, могут быть контейнеры .wav у которых внутри сидит .mp3 стрим.
Плеер задумывался как модульный возможностью быстрого расширения для проигрывания других форматов и для использования разных бэк-эндов. В основе всего интерфейс источника звука, абстрактный класс iAudioSource, и интерфейс аудиосистему iAudioSubsystem.
class iAudioSource
{
public:
iAudioSource()
: m_Looping( false )
{}
virtual void BindDataProvider( const std::shared_ptr& Provider ) = 0;
virtual void Play() = 0;
virtual void Stop() = 0;
virtual bool IsPlaying() const = 0;
virtual bool IsLooping() const { return m_Looping; }
virtual void SetLooping( bool Looping ) { m_Looping = Looping; }
private:
bool m_Looping;
};
class iAudioSubsystem
{
public:
virtual ~iAudioSubsystem() {};
virtual void Start() = 0;
virtual void Stop() = 0;
virtual std::shared_ptr CreateAudioSource() = 0;
virtual void SetListenerGain( float Gain ) = 0;
};
Единственные реализации этих интерфейсов используют OpenAL для вывода звука, поскольку поддержка это API на всех трех платформах вполне достойная.
Чтобы декодировать различные форматы в PCM создаем интерфейс iWaveDataProvider.
class iWaveDataProvider
{
public:
virtual ~iWaveDataProvider() {};
virtual const sWaveDataFormat& GetWaveDataFormat() const = 0;
virtual const uint8_t* GetWaveData() const = 0;
virtual size_t GetWaveDataSize() const = 0;
virtual bool IsStreaming() const { return false; }
virtual bool IsEndOfStream() const { return false; }
virtual void Seek( float Seconds ) {}
virtual size_t StreamWaveData( size_t Size ) { return 0; }
};
И для удобства вот такую фабрику:
std::shared_ptr CreateWaveDataProvider( const char* FileName, const std::shared_ptr& Data );
Различные реализации iWaveDataProvider используют сторонние библиотеки для декодирования аудио форматов. Получилось весьма компактно и пригодно для дальнейшего расширения функциональности.
Проект с исходниками доступен здесь: github.com/corporateshark/PortAMP
Возможно однажды добавлю поддержку FLAC, но пока совершенно нет стимула — в домашней коллекции файлов такого формата нет.