Pull to refresh

Мультиплатформенный аудио плеер на C++ и OpenAL

Reading time 3 min
Views 33K
Так сложилось, что большую часть жизни я пользовался 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<iWaveDataProvider>& 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<iAudioSource> 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<iWaveDataProvider> CreateWaveDataProvider( const char* FileName, const std::shared_ptr<clBlob>& Data );

Различные реализации iWaveDataProvider используют сторонние библиотеки для декодирования аудио форматов. Получилось весьма компактно и пригодно для дальнейшего расширения функциональности.

Проект с исходниками доступен здесь: github.com/corporateshark/PortAMP

Возможно однажды добавлю поддержку FLAC, но пока совершенно нет стимула — в домашней коллекции файлов такого формата нет.

Поддержка FLAC теперь тоже есть.
Tags:
Hubs:
+18
Comments 32
Comments Comments 32

Articles