Дело в том, что за свою программистскую карьеру мне пришлось написать пару мультиплатформных аудио систем для игровых движков: для 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 теперь тоже есть.
Комментарии (31)
poxu
04.09.2015 15:40+1Я как раз пытаюсь найти что-то в этом духе для себя под Windows. Я правда искал что-нить в духе cmus. То есть с текстовым UI.
Как вы пользуетесь плеером? Я посмотрел код и создаётся впечатление, что он умеет только проигрывать файлы по одному. Или я чего-то не так понял?CorporateShark
04.09.2015 16:03Да, пока только по одному. Плейлисты редко использую, обычно просто запускаю одну песню и слушаю с автоповтором пока не надоест. Это не значит, что пулл-риквест с реализацией плейлистов не будет принят :)
poxu
04.09.2015 16:11Ну, может для начала прикрутить возможность передачи списка файлов в качестве аргумента? И играть их по очереди?
CorporateShark
04.09.2015 16:30По сложности примерно одно и то же, но всё равно будет день нужен, чтобы это сделать. Пока в приоритете поддержка FLAC.
AlexBin
04.09.2015 17:13Может воспроизведение списков лучше оставить другому модулю, который будет в соответствии со списком дергать этот движок из командной строки? Так сохранится концепция модульности. Даже с фейдингом проблем не будет. Общение (паузу, перемотку) можно сделать через пайпы. Или нет?
poxu
04.09.2015 17:03У вас треки длинные наверное?
CorporateShark
04.09.2015 17:08Обычные. Просто бесконечный луп ставлю.
poxu
04.09.2015 17:42+2Если я возьму свою любимую песню и поставлю играться в бесконечный луп, то через час эта песня станет моей самой нелюбимой песней. У вас такого эффекта нет? Или может быть у вас треки без слов?
DeXPeriX
04.09.2015 15:40Спасибо за наводку на MiniMP3, приятная штука. Ещё бы что-то подобное, но под public domain (и ещё б и для OGG)… И соответственно сразу вопрос. Ваш плеер лицензирован под MIT, но использует LGPL-библиотеку. Значит, по идее не должен распространяться в бинарниках, статически слинкованных с MiniMP3?
ModPlug чуть ли не единственный в Linux у меня воспроизвёл vibe-fs.it без щелчков. DUMB щелчки добавляет даже под вендой. XMP под вендой не тестил, но в Linux щёлкает жутко. Да даже некоторые трекеры этим не гнушаются… Так что, возможно, тоже буду юзать. Спасибо за ещё один плеер. И да, FLAC — нужен! ^_^CorporateShark
04.09.2015 16:08Там LGPL 2.1. Читаем: «A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a „work that uses the Library“. Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.». Вот здесь комментарий: en.wikipedia.org/wiki/GNU_Lesser_General_Public_License Итого: если доступны сырцы для самостоятельной сборки (чтобы использовать новую версию LGPL библиотеки), то можно статически линковаться и распространять бинари программы, которая использует LGPL библиотеку. Если сырцы вообще не давать (не наш случай), то можно линковаться только динамически.
FLAC будет по мере наличия свободного времени. А еще на GitHub есть кнопка Fork ;)
CorporateShark
04.09.2015 19:40+1FLAC оказался проще, чем я думал. Пока прототип: github.com/corporateshark/PortAMP/tree/flac
DeXPeriX
05.09.2015 18:15+1Спасибо за Flac! Проверил в линуксе, работает!
CorporateShark
05.09.2015 18:17На здоровье! Влил в основую ветку. Теперь можно подумать и о плейлистах.
monah_tuk
05.09.2015 00:49С консольными плеерами ситуация получше: mpg123 и mpg321 практически идеально делают именно то, что надо. Вот только появилось одно большое «но». Они не умеют играть .ogg и трекерную музыку (.it, .mod, .xm, .s3m и прочие), которой тоже накопилось достаточно и расставаться с ней совершенно не хотелось.
Извольте, а чем, тогда, вам не угодили консольные mplayer или ffplay из комплекта FFmpeg. Последний точно под мак есть. Ровно как и под Linux и Windows.CorporateShark
05.09.2015 00:59Не играют трекерную музыку, также как и mpg123/321.
monah_tuk
06.09.2015 04:40а можно пример трека?
CorporateShark
06.09.2015 14:56monah_tuk
06.09.2015 16:58Скачал оттуда xm, mod и it. Проигралось всё. Логи: pastebin.com/Hk58TCz2
там же видно какая версия FFmpeg и с какими библиотеками собиралась. Насколько я понимаю, поддержку обеспечивает libmodplug, эта бинарная сборка (ссылка с главного сайта ffmpeg.org): evermeet.cx/ffmpeg тоже собрана с libmodplug. Эта (ссылка тоже с официального сайта): ffmpegmac.net — непонятно.
mplayer, действительно, отказался хавать. cvlc (консольная версия vlc) тоже переварила. mpv не проверял.
Единственно, что за FLAC, который в одном файле куча треков не скажу, а во всём остальном FFmpeg достаточно неплохо переваривает, что ему подсунут.CorporateShark
06.09.2015 17:08Круто. Насчет ffmpeg в mpg123/321 не знал.
monah_tuk
06.09.2015 17:43Пардон, но что значает «ffmpeg _В_ mpg123/321»? В составе FFmpeg есть проигрыватель ffplay — я им играл :)
CorporateShark
07.09.2015 00:39Имею в виду
brew install mpg123
и потом запускатьmpg123
monah_tuk
07.09.2015 15:51так при чём тут ffmpeg В mpg123/321? :-) т.е. я вашего хода мыслей не понял.
maaGames
06.09.2015 09:07Давно собираюсь написать «свой» плеер. Из существующих для Windows меня устроил только 1by1, но он иногда папки проскакивает, да и кое-какого функционала нет в нём. Собирался использовать библиотеку bass (её уже приходилось использовать, а производительность меня мало волнует в контексте плеера), но почитаю и про упомянутые вами библиотеки. Мне нужно лишь mp3 и ogg.
А ваша книжка на русском будет издаваться? С удовольствием бы почитал. В ней есть что-нибудь про трекерную музыку под андроид?CorporateShark
06.09.2015 14:55Да, там есть код для работы с libmodplug под Андроид. Насчет русской версии книжки ничего сказать не могу, все права на перевод книжки у издателя.
Nikobraz
07.09.2015 04:44Мне evilplayer.net нравился, я его лет 8 назад даже на Delphi повторял, когда учился.
stalkerg
07.09.2015 11:29+1Я везде использую консольный mplayer или fplay или mpv.
Работают одинаково на всех платформах. :)
DeXPeriX
Если не секрет, зачем Вам очень плотная интеграция с командной строкой? И зачем под все платформы обязательно один и тот же плеер? Под каждую операционку имею свой, наиболее удобный. Но везде настроены одинаковые глобальные клавиши, большую часть времени интерфейса плеера не вижу вообще, дискомфорта не ощущаю.
CorporateShark
Дело привычки. Очень удобно когда на всех платформах все одинаково на уровне рефлексов. Начинал писал исключительно для себя. Когда увидел, что проект работает, то решил выложить в публичный доступ — может кому-то тоже пригодится.