Мультиплатформенный аудио плеер на 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, но пока совершенно нет стимула — в домашней коллекции файлов такого формата нет.

© Habrahabr.ru