Звуковая библиотека BASS

Существует множество библиотек для обработки звука. Если вас, как и меня интересует использование подобных вещей и вы не знаете — с чего начать, добро пожаловать. А в качетве объекта для экспериментов выберем аудиобиблиотеку BASS.

BASS audio library

О библиотеке

Сталкиваясь со всяким самодельным (условно) ПО для учета устных ответов учащихся, заметил присутствие некой bass.dll в папке приложения. Причем приложений было много, а библиотека одна.

Погуглив инфу по BASSу обнаружил следующий ресурс. На главной странице есть раздел Licensing, в котором написано BASS is free for non-commercial use. Тоесть басс бесплатен для некоммерческого использования. Вот и попользуем его некоммерчески.

Первая компиляция

Сразу оговорюсь, что проект будем рассматривать для Linux. Все манипуляции можно провернуть в ОС AV Linux.

На главной странице сайта Un4seen Developments щелкнем по кнопке download напротив картинки с пингвином. Полученный архив можно распаковать в любую удобную для вас папку. Что мы видим внутри? Здесь есть справка в формате .chm, что важно, а также куча папок с примерами и разделяемые библиотеки с make-файлами.

Так как начинать нужно с простого, откроем папку contest. Внутри один исходник и один make-файл. Всё, что нужно сделать — это открыть теминал в этой папке и выполнить команду make. Повторюсь, что в AV Linux компиляция проходит без проблем.

BASS contest make
Компиляция прошла успешно

Теперь в этом же окне откроем полученный бинарник командой ./contest

Вывод будет следующий:

BASS simple console player
usage: contest [-l] [-d #] <file> -l = list devices -d = device number

Из вывода ясно, что программа поддерживает две команды: «-l» для вывода списка аудиоустройств и «-d» для выбора устройства из этого списка с указанием звукового файла.

Заглянув в исходник можно заметить такой фрагмент:

// try streaming the file/url 
if ((chan = BASS_StreamCreateFile(FALSE, argv[filep], 0, 0, BASS_SAMPLE_LOOP)) || (chan = BASS_StreamCreateURL(argv[filep], 0, BASS_SAMPLE_LOOP, 0, 0)))

Таким образом, из написанного видно, что программа открывает файл для воспроизведения в первую очоредь. Если загрузка не удалась, то имя файла интерпретируется как URL и откравется поток по данному URL. Воспользуемся этим свойством. Воспроизведем, например что-нибудь с данного ресурса.

В первую очередь узнаем, какими устройствами воспроизведения мы располагаем. Выполним команду «./contest -l«, найдем устройство Default и начнем воспроизведение. Результат этого процесса на рисунке ниже.

BASS simple console player
Процесс воспроизведения

Изучаем исходник и выделяем нужное

Как правило, фирменные исходники даже самых простых программ написаны очень качественно и содержат массу украшательств и прочих интересных фишек. Лично мне эти украшательства создают препятствия для понимания сути происходящих процессов.

Среди папок с примерами создадим свою и скопируем в нее makefile из contest и добавим файл mytest.c. В файле makefile заменим значение параметра TARGET на mytest.

О функциях, необходимых для простейшего запуска

BASS_GetVersion(). Так как версия разделяемой библиотеки и заголовочных файлов может разниться, следует проверить сходство этих версий.

BASS_SetConfig(BASS_CONFIG_NET_PLAYLIST, 1). Вот и настало время заглянуть в справку. В документации сказано, что мы таким образом устанавливаем возможность обрабатывать плейлисты, состоящие из URL. Тоесть можно в качестве входного файла подать PLS или M3U файл. Существуют так называемые «загрязненные» плейлисты (среди списка песен может быть адрес другого плейлиста). Глубина воспроизведения этого добра регулирется флагом BASS_CONFIG_NET_PLAYLIST_DEPTH.

BASS_Init(device, 44100, 0, 0, NULL). Как видно из названия — инициализация устройства. Если device равно -1, то выбирается устройство по умолчанию, если 0 — устройство no sound, если 1 — первое реальное устройство и т.д. Далее идет частота дискретизации, набор флагов, идентификатор окна в Windows (для DirectSound), идентификатор класса для DirectSound.

BASS_StreamCreateFile(FALSE, argv[filep], 0, 0, BASS_SAMPLE_LOOP). FALSE в данном случае говорит о чтении файла, а не фрагмента из памяти (TRUE). argv[filep] — имя файла для чтения. Далее идут смещение (для случая с файлом) и длина воспроизводимого фрагмента. Здесь 0 означает, что файл будет воспроизведен полностью. Последний флаг говорит нам о циклическом воспроизведении. Если процедура загрузки выполнена успешно, функция вернет идентификатор потока.

BASS_ChannelGetLength(chan, BASS_POS_BYTE). Получаем длину записи в байтах.

BASS_ChannelBytes2Seconds(chan, pos). Пересчет длины записи в байтах в секунды.

BASS_ChannelPlay(chan, FALSE). Воспроизведение потока (канала). Второй параметр отвечает за перезапуск фрагмента (это если не вдаваться в подробности).

BASS_ChannelIsActive(chan). Фактически предыдущая функция возвращает управление основной программе, доверяя воспроизведение отдельному процессу. В следствии чего, неплохо было бы знать состояние нашего канала (воспроизводится, остановлен, подгружается и т.д.).

BASS_ChannelGetLevel(chan). В ходе воспроизведения получаем текущий уровень звуковой волны. Причем возвращаемое двойное слово (DWORD) содержит информацию об уровнях правого и левого каналов. Если мы хотим получить уровень левого канала, берем младшее слово LOWORD, если правого — HIWORD.

BASS_ChannelGetPosition(chan, BASS_POS_BYTE). Возвращает текущую позицию воспроизведения в байтах (которые, опять же, можно перевести в секунды).

BASS_Free(). Освобождает всю память, связанную с нашими аудиоресурсами.

Шаг 1. Каркасс

#include <stdlib.h>
#include <stdio.h>
#include "bass.h"

#include <sys/time.h>
#include <string.h>
#include <unistd.h>

void main(int argc, char* argv[]) {
	
	return;
}

Шаг 2. Работа с аргументами

Для начала будем считать, что имя файла передается в качестве первого аргумента командной строки. Перед return допишем следующие строки:

	if(argc < 2) {
		printf("Syntax: mytest <audio file>\n");
		return;
	};

Шаг 3. Проверка версий

Соответствует ли библиотека? Добавим следующий фрагмент.

	if(HIWORD(BASS_GetVersion()) != BASSVERSION) {
		printf("An incorrect version of BASS was loaded");
		return;
	};

Шаг 4. Инициализация

Инициализируем устройство по умолчанию:

	int device = 1;

	if(!BASS_Init(device, 44100, 0, 0, NULL)) {
		printf("Can't init device!");
		BASS_Free();
		return;
	};

Шаг 5. Загрузка файла и получение его параметров

Объявим переменные для канала, его состояния, текущего времени, уровня сигнала, длины трека и текущей позиции воспроизведения. Если файл не удастся открыть, выведем соответствующее сообщение. В противном случае продемонстрируем длину аудиоданных в байтах и в секундах.

DWORD ch, a, time, level;
QWORD len, pos;

if((ch = BASS_StreamCreateFile(FALSE, argv[1], 0, 0, BASS_SAMPLE_LOOP))) {
	len = BASS_ChannelGetLength(ch, BASS_POS_BYTE);
	if(len != -1) {
		printf("Length in bytes: %u\n", len);
		printf("Length in seconds: %u\n", BASS_ChannelBytes2Seconds(ch, len));
	};
} else {
	printf("Can't open file \"%s\"\n", argv[1]);
	return;
};

Шаг 6. Воспроизведение

Теперь можно начать процесс воспроизведения.

BASS_ChannelPlay(ch, FALSE);

while(a = BASS_ChannelIsActive(ch)) {
	level = BASS_ChannelGetLevel(ch);
	pos = BASS_ChannelGetPosition(ch, BASS_POS_BYTE);
	time = BASS_ChannelBytes2Seconds(ch, pos);
	printf(" - %u:%02u - L ", time / 60, time % 60);
	if(a == BASS_ACTIVE_STALLED) {
		printf("-     buffering: %3u%%     -", (DWORD)BASS_StreamGetFilePosition(ch, BASS_FILEPOS_BUFFERING));
	} else {
		printf("%05u : %05u R", LOWORD(level), HIWORD(level));
	};
	printf("\r");
	fflush(stdout);
	usleep(50000);
};
printf("\r                                      \r");
BASS_Free();

Пока канал активен (файл воспроизводится), получаем текущий уровень сигнала, текущую позицию, переводим позицию в секунды, выводим время на экран. Если файл подгружается, выводим состояние процесса, в противном случае отображаем уровни левого и правого каналов в виде числовых значений. Затем очищаем буфер для stdout и делаем паузу в 50000 микросекунд.

Особенности linux

Вот собственно и вся программа. Но в ней есть одна деталь — трек вы будете слушать циклически без возможности его прервать. К тому же, если выполнить команду Ctrl+C в терминале, работа BASS будет завершена некорректно. Так как в стандартных библиотеках для linux нет аналога функции _kbhit() для проверки нажатия клавиши без прерывания программы, авторы contest.c реализовали эту функцию.

int _kbhit()
{
	int r;
	fd_set rfds;
	struct timeval tv = { 0 };
	struct termios term, oterm;
	tcgetattr(0, &oterm);
	memcpy(&term, &oterm, sizeof(term));
	cfmakeraw(&term);
	tcsetattr(0, TCSANOW, &term);
	FD_ZERO(&rfds);
	FD_SET(0, &rfds);
	r = select(1, &rfds, NULL, NULL, &tv);
	tcsetattr(0, TCSANOW, &oterm);
	return r;
}

Чтобы можно было прервать воспроизведение нажатием клавиши, добавим данный фрагмент и заголовочный файл termios.h в наш исходник. А текст while(a = BASS_ChannelIsActive(ch)) заменим на while(!_kbhit() && (a = BASS_ChannelIsActive(ch)))

Результат всех перечисленных действий на рисунке ниже.

Простейшая программа с BASS
Длина в секундах вызывает вопросы

Полезные ссылки

http://www.un4seen.com/doc/#bass/bass.html — документация

http://www.un4seen.com/bass.html — главная

Добавить комментарий

Ваш e-mail не будет опубликован.