Дело в том, что за свою программистскую карьеру мне пришлось написать пару мультиплатформных аудио систем для игровых движков: для 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 теперь тоже есть.

