Разработка веб-приложения музыкального аудио-стриминга

Проектирование архитектуры и базы данных приложения музыкального аудио-стриминга. Способы воспроизведения звука в браузер. Алгоритм организации стримингового процесса. Реализация процессов остановки, воссоздания воспроизведения, прокрутки композиции.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 16.08.2020
Размер файла 2,7 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http: //www. allbest. ru/

Введение

Стриминговые сервисы являются одним из самых популярных способов потребления музыкального контента на сегодняшний день. Проникнув на рынок аудиопродукции в начале в XXI века, дaнный вид услуг начал развиваться настолько динамично, что за последние несколько лет сил сильно изменил устоявшиеся тенденции музыкальной индустрии. Плaтформы, обеспечивающие потоковую aудио трансляцию в режиме реального времени, оказывают влияние не только на процесс пoтребления, но и на подход к созданию треков. Дoступность, удобство прослушивания, индивидуализация вещания, персонализация музыки и полная свобода выбора информации - идейная основа музыкальных стриминговых сервисов, которая активно смещает интерес аудитории в сторону их использования.

В рамках данной выпускной квалификационной работы была поставлена цель - разработать веб-приложение музыкального аудио-стриминга. Для достижения поставленной цели необходимо определить понятие музыкального аудио-стриминга и изучить, в рамках этой задачи, существующие аналогичные решения. Далее, составить набор требований к разрабатываемой системе, проанализировать их и после этого переходить, непосредственно, к проектированию и реализации. Таким образом, требуется решить следующие задачи:

1) выявить требования к системе;

2) проанализировать варианты использования;

3) спроектировать архитектуру приложения;

4) спроектировать архитектуру базы данных;

5) рассмотреть способы воспроизведения и передачи звука в браузер и выбрать наиболее подходящую технологию;

6) изучить техническую документацию по выбранной технологии;

7) разработать алгоритм организации стримингового процесса с помощью выбранного стека технологий;

8) реализация приложения.

Дипломная работа структурирована следующим образом:

1) В первой главе приведено определение понятия музыкального аудио-стриминга, проведен анализ существующих веб-сервисов, предоставляющих данную услугу.

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

3) Во третьей главе приведен обзор инструментария реализации и выбранной для внедрения аудиофайлов на веб-страницы технологии, описаны варианты решения задачи организации стримингового процесса, используя данную технологию.

4) В четвертой главе рассмотрена структура файла с аудиоданными, приведен алгоритм расчета его длительности проигрывания, а также предложена реализация стримингового процесса.

5) Пятая глава посвящена реализации процессов остановки, воссоздания воспроизведения, а также возможности прокрутки композиции.

Глава 1. Понятие музыкального аудио-стриминга и анализ существующих решений

стриминг алгоритм прокрутка звук

С нaчaлом глобальной интернетизации в 2000-x годах, когда стало ясно, что формат CD становится менее востребованным, а вся музыка становится доступной в несколько кликов, могло показаться, что процесс ее потребления отныне будет хаотичным: торрент-трекеры, файлообменники, локальные сети, с одной стороны, способствовали распространению треков и альбомов, с другой -- ломали отлаженные десятилетиями схемы продвижения и донесения записей до слушателя. В тот момент, когда музыку, казалось, можно было окончательно провозгласить свободной и бесплатной, ситуацию изменило распространение сервисов потокового воспроизведения. Одним из первых на новом рынке стал шведский сервис Spоtify, основанный в 2006 году. Именно он первым предложил модель потокового воспроизведения музыки без скачивания на носители. На конец 2017 года аудитория Spоtify составляла 159 миллионов слушателей в месяц, 71 миллион из которых оплачивал подписку. Впоследствии крупнейшие музыкальные сервисы также стали переходить на модель потоковой передачи данных.

1.1 Понятие музыкального аудио-стриминга

Cтриминг (англ. streaming -- потоковый) -- это способ передачи потока медиа данных от провайдера к конечному пользователю, при котором контент находится на удаленном сервере и транслируется пользователю непрерывно небольшими частями в режиме реального времени. Особенность такого метода в том, что пользователю не нужно дожидаться полной загрузки аудио файла для того, чтобы начать прослушивание. Потоковая передача позволяет осуществлять передачу и воспроизведение мультимедиа одновременно.

Скорость загрузки фрагментов файла как правило ограничена только пропускной способностью интернета пользователя. С нынешним даже самым бюджетным тарифом на интернет можно без проблем прослушивать музыку и просматривать видео из стриминговых сервисов. Чтобы обеспечить непрерывное воспроизведение потока в случае кратковременного прерывания соединения, потоковые сервисы используют технологию буферизации данных, которая означает, что когда мультимедийные данные принимаются, то ограниченный объем этих данных сохраняется во временной памяти, называемой буфером. По сути, буферизация действует как временная задержка перед началом воспроизведения, поскольку пользователю приходится ждать пока буфер памяти заполнится приходящими фрагментами.

Мультимедиа потоки бывают двух видов: по запросу или живыми. Потоки информации, вызываемой по запросу пользователя, в отличии от живых потоков хранятся на серверах продолжительный период времени и могут быть доступны для пользователя в любой удобный для него момент времени.

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

1.2 Анализ существующих сервисов

Со временем функционал музыкальных стриминговых приложений начинает разрастаться: развиваются рекомендательные системы для персонализированного контента, предлагаются авторские и редакционные плейлисты и радиостанции, а также возможность скачивать треки на устройство и слушать музыку в автономном режиме. Большинство музыкальных приложений, представленных на данный момент на рынке, обладают подобным базовым функционалом, но, чтобы оставаться конкурентноспособными каждый из них также имеет свои уникальные особенности, которые выделяют его среди других. Среди самых популярных стриминговых музыкальных сервисов, которые прочно заняли свои позиции в этой нише - Spotify, SoundCloud, Deezer и Tidal.

Лидерство среди них по-прежнему сохраняется за Spotify, у которого охват аудитории в несколько раз превосходит конкурентов. Он предоставляет функциональность полноценной музыкальной социальной сети, использует алгоритмы подбора композиций по предпочтениям пользователя, предлагает кураторские плейлисты ежедневно и подборки от музыкальных экспертов каждую неделю Discover Weekly, а также имеет спортивный режим Running, автоматически подбирающий музыку в соответствии с темпом вашего бега. Недавно в Spotify была запущена новая программа Spotify For Artists, по условиям которой загружать музыку на Spotify смогут независимые музыканты без лейблов и зарабатывать на ней. Запустив эту функцию, Spotify составил конкуренцию другому музыкальному сервису SoundCloud, который давно стал основной площадкой для дистрибуции собственной музыки.

Музыкальный потоковый сервис Tidal, как и SoundCloud, ориентирован на музыкантов, но лишь отчасти, он помогает начинающим артистам чтобы о них узнала широкая аудитория, а также у него хорошо налажена коммуникация между исполнителями и поклонниками. Tidal предлагает пользователям эксклюзивный контент, поскольку эта платформа принадлежит нескольким популярным артистам, ей проще заполучать треки исполнителей до того, как их запустят в чарты. Также Tidal дает уникальную возможность своим подписчикам смотреть выступления любимых исполнителей в прямом эфире и в отличном качестве до 4608 kbps. Французский стриминговый сервис Deezer предлагает полноценный lossless-стриминг в формате FLAC с битрейтом 1411 Кбит/с, интерактивные тексты к песням, а также опцию Flow с персональными подборками незнакомых треков вперемешку с собственной коллекцией пользователя.

Глава 2. Требования и проектирование архитектуры приложения

2.1 Определение требований к системе

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

Функциональные требования к системе:

ФТ1. Регистрация/авторизация

ФТ2. Система предоставляет пользователю возможность слушать музыку.

ФТ3. Система поддерживает стандартный функционал аудио-проигрывателя, такой как Play/Pause, Skip, Mute/Unmute, изменение уровня громкости, временная шкала с возможностью прокрутки композиции и возврата к предыдущей мелодии.

ФТ4. Возможность выбора композиции для воспроизведения из списка или с помощью поиска.

ФТ5. Администратор приложения имеет возможность добавлять композиции в коллекцию.

Нефункциональные требования к системе:

ФТ1. Потоковая передача аудио данных с сервера

ФТ2. Прием и воспроизведение аудио потока на клиенте

Более детальные требования по организации потоковой передачи аудиофайла будут сформулированы после того, как будет дано определение понятия «стриминга», будет выбрана технология для воспроизведения аудио в браузере, а также формат транслируемых аудиоданных.

Описание вариантов использования актера «Пользователь»:

1. Вариант использования «Регистрация/Авторизация».

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

2. Вариант использования «Навигация»

Для перемещения по страницам приложения пользователь может воспользоваться меню и дополнительными гиперссылками на страницах.

3. Вариант использования «Поиск композиций».

Искать композиции для прослушивания пользователь может по их названию и/или исполнителю, вводя ключевые слова в текстовое поле поиска.

4. Вариант использования «Слушать музыку».

Когда пользователь желает прослушать какую-нибудь композицию из отображенного на странице списка, он нажимает на нее. Система отправляет запрос на сервер для начала стриминга выбранной композиции. По мере непрерывного получения небольших частей музыкального файла в режиме реального времени происходит их параллельное воспроизведение. Таким образом пользователь моментально начинает прослушивать композицию онлайн без ожидания полной загрузки трека с сервера.

5. Вариант использования «Управлять проигрывателем».

Пользователь собирается взаимодействовать с плеером. Данный вариант использования является обобщенным и включает в себя:

5.1. Регулировать громкость - при несоответствии уровня громкости предпочтениям пользователя он может его отрегулировать путем перемещения ползунка по шкале уровня громкости.

5.2. Приостановить прослушивание - если пользователь хочет временно остановить прослушивание, он жмет на кнопку паузы.

5.3. Пропустить композицию - когда пользователь решает, что хочет услышать следующую композицию, он жмет на кнопку пропуска.

5.4. Перемотать композицию на заданный интервал - когда пользователь хочет изменить точку воспроизведения, он перемещает ползунок вдоль временной шкалы в нужную позицию.

Описание вариантов использования актера «Администратор»:

1. Вариант использования «Добавить композицию».

Администратор выбирает композицию, которую он хочет добавить. Система добавляет композицию и её данные в медиатеку, запрашивая дополнительные данные при необходимости.

2.2 Анализ требований

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

Регистрация/Авторизация

Рис.1 Use-case UML-диаграмма авторизации/регистрации

Когда пользователь попадает на страницу для входа, ему предлагается на выбор два способа авторизации в системе:

1) по e-mail:

1.1) регистрация: пользователю необходимо заполнить форму со стандартными полями: Логин, Пароль, E-mail (корректность введенных данных проверяется динамически, по мере ввода символов); затем отправить форму, нажав на кнопку «Зарегистрироваться». После регистрации на указанный в форме e-mail должен прийти запрос на подтверждение регистрации. Пользователю необходимо перейти по ссылке, присланной в письме, после чего ему сообщат об успешной активации его логина и пароля для доступа к услугам сервиса. В случае если пользователь уже авторизован, система сообщит об этом и появится ссылка на восстановление пароля.

1.2) авторизация: для входа в систему пользователю необходимо в форме указать Логин и Пароль и нажать на кнопку «Войти». Если Логин или Пароль окажутся неправильными или учетной записи с введенным логином не существует, то система предупредит об этом и предложит пройти процедуру регистрации.

2) с помощью существующей учетной записи Google. Когда пользователь нажимает на кнопку «Войти через Google», запускается поток авторизации через Google и он переводит пользователя на сайт Google. Оказавшись там, он входит в систему со своими учетными данными Google, после чего они будут перенаправлены на страницу подтверждения. На странице подтверждения у пользователя будет запрошено разрешение на передачу информации о своей учетной записи Google на сторонний сервис, т.е. на разрабатываемый. После того, как он разрешит поделиться своей информацией со сторонним сайтом, пользователь будет перенаправлен на наш сайт, откуда был инициирован поток входа в Google. На этом этапе пользователь вошел в систему с помощью Google, а наша система получила доступ к информации профиля пользователя, которую можно использовать для создания учетной записи и входа в систему.

Навигация

Для зарегистрированного пользователя доступна:

1) Страница со списком песен, предложенных для прослушивания системой;

Для незарегистрированного пользователя:

1) Страница для входа с вкладками регистрации/авторизации.

Для администратора:

1) Страница с добавлением композиций в медиатеку.

Слушать музыку

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

Управлять проигрывателем

Рис.2 Use-case UML-диаграмма управления проигрывателем

Вариант использования «Управлять проигрывателем» олицетворяет стандартный функционал взаимодействия пользователя с аудиоплеером. Когда играет музыка пользователь может использовать набор кнопок: чтобы остановить воспроизведение или, наоборот, возобновить; чтобы повысить или понизить уровень громкости, а также полностью отключить звук или восстановить уровень громкости, который был установлен до отключения; чтобы полностью пропустить текущую композицию и перейти к прослушиванию следующей; чтобы вернуться к началу воспроизведения или к предыдущей композиции.

Сменить композицию

Когда текущая композиция заканчивается, либо пользователь намеренно пропускает композицию запускается сценарий смены композиции. Приложение принимает от пользователя запрос и отправляет его проигрывателю. Проигрыватель обрабатывает запрос, получает данные об объекте из медиатеки и воспроизводит композицию. Проигрыватель прерывает текущую композицию и запускает новую в автоматическом режиме.

2.3 Проектирование архитектуры приложения

Музыкальный стриминговый сервис реализован с использованием клиент-серверной архитектуры. Взаимодействие веб-сервера и клиента осуществляется посредством технологии Socket.IO. Socket.IO - это библиотека JavaScript для кросс-браузерной поддержки технологии WebSockets, которая предоставляет легкий и удобный уровень абстракции, позволяющий использовать все возможные технологии обмена данными броузера с сервером в реальном времени (WebSocket, Adobe Flash Socket, AJAX long polling, AJAX multipart streaming, Forever Iframe, JSONP Polling), в т.ч. обеспечивает работу в старых IE и т.д. Socket.IO библиотека состоит из двух частей: клиентской библиотеки, которая запускается в браузере, и серверной библиотеки для node.js. Оба компонента имеют идентичный API:

io.sockets -- выбор всех подключенных клиентов

io.sockets.sockets[ID] -- выбор конкретно взятого клиента с id ID

socket -- выбор «текущего» клиента

socket.send(TEXT) -- «базовое» событие, отправка сообщения TEXT

socket.json.send({}) -- отправка сообщения в формате JSON

socket.broadcast.send -- отправка сообщения всем клиентам, кроме текущего

socket.emit(EVENT, JSON) -- отправка пользовательского события EVENT с данными JSON, может использоваться для переписывания стандартных событий 'connected', 'message' и 'disconnect'

socket.on(EVENT, CALLBACK) -- вызов функции CALLBACK при возникновении события EVENT

2.3.1 Архитектура серверной части

Функционал сервера схематично представлен на следующем рисунке:

Рис.3 Функционал сервера

Здесь обработчик события «getTracks» вызывает метод getTracks из API для работы с базой данных, передав ему параметр pathname - URL адрес запроса без домена, чтобы он мог вернуть соответствующий этому маршруту список объектов, содержащих информацию о композиции, такую как имя исполнителя, название трека, альбома и т.д.

Метод getTracks в свою очередь вызывает функцию getListId, которая находит в таблице «list» из базы данных и возвращает Promise с id списка песен, соответствующего запрошенному адресу. После того как id будет получено, по нему находятся id всеx композиций в таблице «track_list_relationship» с помощью функции getTracksByListId. Для каждого id из списка вызывается метод getTrackById, возвращающий объект с информацией о каждой композиции по его id. Все запросы к базе данных отправляются в методе makeRequest принимающим строку SQL-запроса в качестве аргумента.

Обработчик события «getTrack» принимает id трека, воспроизведение которого запросил пользователь. Затем он получает из базы данных путь, по которому хранится аудиофайл, считывает его размер и продолжительность проигрывания и отправляет эти данные вместе с событием «metadata».

Слушатель события «play» принимает startSize - это номер байта, с которого необходимо начать воспроизведение файла, далее он считывает файл в поток для чтения частями, и как только очередной фрагмент файла будет готов для отправки, обработчик вызовет событие «audio» для передачи этой порции данных клиенту.

Обработчик события «stopLoad» останавливает чтение файла и отправку его фрагментов, если они еще не завершены.

2.3.2 Архитектура клиентской части

Клиентская часть представляет собой одностраничное приложение (SPA), состоящее из компонентов, некоторые из которых могут обладать своим состоянием. Иерархия этих компонентов представлена на рисунке ниже:

Рис.4 Иерархия React компонентов

На вершине иерархии стоит компонент <AudioProvider>, который определяет глобальное состояние для всего приложения с помощью Context API React. и внутри него реализована основная логика работы приложения, связанная с контролем воспроизведения и громкости звука. <AudioProvider> оборачивает все компоненты приложения чтобы обеспечить доступ к работе со звуком и глобальному состоянию из всех компонентов приложения.

Компонент <MainPage> и все его потомки выводятся только для авторизованных пользователей и являются приватными, доступ к ним осуществляется после входа в систему и получения токена, с помощью которого контролируется отображение этих компонентов. <MainPage> является оберткой для всех приватных страниц приложения и состоит из таких компонентов как:

<Menu> - боковая панель навигации, которая определяет переключение между монтированием компонентов внутри обертки <MainPage>;

<Burger> - кнопка, отвечающая за сворачивание и разворачивание бокового меню;

<AudioPlayer> представляет собой проигрыватель, который неизменно присутствует на страницах в самом низу и отвечает за управление воспроизведением композиций и регулированием громкости с помощью дочерних компонентов <Draggable> и <Controllers>, а также отображает информацию о проигрываемом треке.

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

<Controllers> компонент предоставляет набор элементов управления для остановки или возобновления проигрывания, возврата к началу воспроизведения, перехода к предыдущей композиции и к следующей.

<Menu> компонент отвечает за навигацию по страницам приложения, которые монтируются внутрь обертки <MainPage>. <Menu> представляет собой список ссылок для маршрутов, прописанных в <Router>. React Router определяет набор маршрутов и, когда к приложению, приходит запрос, то Router выполняет сопоставление запроса с маршрутами. И если какой-то маршрут совпадает с URL запроса, то этот маршрут выбирается для обработки запроса. Также для выбора маршрута определен объект Switch из модуля react-router-dom. Он позволяет выбрать первый попавшийся маршрут и его использовать для обработки. Без этого объекта Router может использовать для обработки одного запроса теоретически несколько маршрутов, если они соответствуют строке запроса.

Каждый маршрут представляет объект Route, в котором через атрибуты определяются шаблон адреса, с которым будет сопоставляться запрошенный адрес URL, и компонент, отвечающий за обработку запроса по этому маршруту.

Маршрут /home сопоставляется с компонентом <HomePage>, поэтому, когда пользователь переходит по этому адресу, роутер размещает компонент <HomePage> внутри <MainPage>. В итоге при переходе по пути /home будут выведены два вложенных компонента внутри «root». Чтобы реализовать такую вложенность с использованием объекта Switch и сделать обертку для нескольких объектов Route был использован шаблон «компонент высшего порядка» (High Order Component,-- HOC). HOC-компонент сгенерирует расширенную версию исходного компонента, обернув его в <MainPage>.

Внутри <HomePage> содержится компонент <TrackList>, который представляет собой таблицу с данными о композициях, загруженных из базы данных. Каждая строка в ней отображает информацию об отдельном треке, и она выведена в отдельный компонент <Track>.

Компонент <WelcomePage> является оберткой для всех страниц, предназначенных для неавторизованного пользователя. Эта обертка добавляется к исходному компоненту также с помощью HOC-компонента. Она представляет собой горизонтальную навигацию, расположенную вверху страницы.

Вложенным компонентом выступает <JoinPage>, который монтирует внутрь DOM-элемента «root» вместе с оберткой при запросе маршрута /join <Router>. <JoinPage> отображает приветственный текст с кнопкой «Join», которая инициирует открытие модального окна, описанного в компоненте <AuthModal>.

Внутри компонента <AuthModal> представлены две вкладки «Sign In» и «Sign Up», переключаясь между которыми пользователю отображаются компоненты <SignIn> и <SignUp> соответственно. Эти компоненты представляют собой формы для авторизации и регистрации.

Для отображения динамических данных в этих компонентах используется единый источник данных в виде дерева состояний. Компоненты могут изменять данное дерево в виде реакции на действия пользователя, но только посредством отправки (dispatch) сообщений - actions, которые маршрутизируются и обрабатываются при помощи специальных функций - reducers, данные функции получают на вход предыдущее состояние дерева состояний, сообщение - action (которое может содержать в себе дополнительные данные), данная функция возвращает новое дерево состояний в результате обработки сообщения. Функция reducer должна представлять из себя чистую функцию, то есть быть детерминированной и не обладающей побочными эффектами (никаких API-вызовов с какими-либо изменениями). Например, пользователь нажимает на кнопку Play, чтобы начать проигрывание, после чего срабатывает обработчик этого события и отправляет (dispatch) сообщение (action) типа ON_PLAY. Данное сообщение попадает в специальную функцию - reducer - onPlay. Эта функция меняет дерево состояний таким образом, что состояние isPlaying - узла div, отвечающего за отображение кнопки паузы и воспроизведения, становится true. Чтобы из любого компонента приложения получить доступ к обновленному состоянию дерева, в нем необходимо подписаться на изменения состояния хранилища. После этого приложение будет знать, что кнопку воспроизведения нужно поменять на кнопку паузы.

Рис.5 Жизненный цикл Redux

Схема архитектуры клиентской части представлена на рисунке:

Рис.6 Диаграмма архитектуры клиентской части

2.4 Проектирование схемы Базы Данных

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

Для реализации системы была реализована следующая схема базы данных:

Рис.7 Схема Базы Данных

Глава 3. Используемые технологии

3.1 Инструментарий реализации

Клиентская часть была реализована с помощью фреймворков React и Redux и написана на языке JavaScript последней редакции ECMAScript 6. Библиотека React позволяет разрабатывать повторно используемые компоненты пользовательского интерфейса с помощью специального HTML подобного синтаксиса JSX.

Для разработки веб-сервера использовался Node.js -- это кроссплатформенная среда, основанная на JavaScript движке V8 из Chrome.

Для реализации базы данных выбрана СУБД PostgreSQL. В качестве Web-интерфейса с помощью которого можно администрировать сервер PostgreSQL, запускать команды и просматривать содержимое таблиц и БД через браузер, был выбран pgAdmin.

3.2 Внедрение аудиофайла на Web-страницу

Аудиосопровождение веб-страниц имеет довольно давнюю, но совсем не примечательную историю. Изначально звуковой контекст мог загружаться в браузер с помощью тега <bgsound>, который позволял автоматически проигрывать midi файлы при открытии сайта. Это решение впервые было предложено в браузере Microsoft Internet Explorer. Довольно скоро его использовать перестали, так как эффект получался навязчивым, и создатели веб-ресурсов, за редким исключением, данную возможность просто игнорировали. В ответ на попытку Microsoft Internet Explorer внедрить звук в браузер разработчики Netscape добавили аналогичную функцию с помощью тега <embed>. Ни одно из этих решений так и не было стандартизовано, как, в принципе, и не было впоследствии наследовано остальными браузерами.

По прошествии некоторого времени на веб-страницы начал возвращаться звук с активным распространением сторонних плагинов. Проигрывать аудио стало возможным с помощью Flash, Silverlight, QuickTime и т.п. Все они хорошо выполняли свою роль со всеми свойственными плагинам способностями и ограничениями. Поэтому возможность иметь инструмент для работы со звуком, поддерживаемый веб стандартами, уже давно будоражила умы разработчиков. С массовым приходом мобильных браузеров, не поддерживающих Flash, проблема стала еще острее.

HTML долгое время оставался безмолвным пока в рамках HTML5 не появился специальный тег <audio>. Он позволяет проигрывать аудио файлы и потоковые данные, контролировать воспроизведение, буферизацию и уровень звука. Чтобы на странице появился аудиоплеер, проигрывающий указанный файл, достаточно добавить тэг на HTML-страницу, который в самом простом виде выглядит следующим образом:

<audio src="sound. mp3" controls >

Атрибут controls здесь отвечает за появление в браузере некоторых элементов управления плеером, таких как громкость и кнопки воспроизведение (пауза). Также важно то, что элемент <audio> имеет собственный минимальный JavaScript API, с помощью которого можно управлять плеером.

Элемент <audio> поддерживается большинством современных браузеров (включая IE9), но проблема в том, что разные браузеры поддерживают разные форматы медиа файлов. Safari, например, может проигрывать MP3, а Firefox не может, и играет OGG-файлы вместо этого.

Элемент <audio> отлично справляется со своим предназначением воспроизводить музыку, но весьма ограничено. У него отсутствует возможность проигрывания коротких звуков и большинство реализаций позволяют воспроизводить только один звук за раз. Что ещё более важно, с помощью <audio> тега нельзя генерировать звук на лету или получить доступ к библиотеке сэмплов для дальнейшей обработки.

Для устранения этих недостатков World Wide Web Consortium (W3C) Audio Working Group в 2011 году разработал новую спецификацию под названием Web Audio API [1]. Она определяет весь API обработки звука в комплекте с генерацией звуков, фильтрами, потоками и доступом к сэмплам. Большая часть производителей браузеров на данный момент представила реализацию данной спецификации кроме Internet Explorer (IE).

Элементы <audio> и Web Audio API практически никак не связаны между собой. Это два независимых, самодостаточных API, предназначенных для решения разных задач. Элемент <audio> подходит для создания простого аудио плеера и (однопоточного фонового аудио) воспроизведения фоновой музыки, а больший контроль и выполнение гораздо более интересных, разносторонних и сложных задач призван обеспечивать Web Audio API. Единственная связь между ними состоит в том, что <audio> элемент может быть одним из источников звука для Web Audio API.

Преимущества Web Audio API:

- Cинхронное воспроизведение аудио (возможность проигрывать сотни семплов одновременно с разницей в миллисекунды, точно планируя начало и конец воспроизведения каждого из них)

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

- Богатые возможности для синтеза колебаний звуковой частоты с различной формой огибающей.

- Работа с многоканальным аудио (Исходя из спецификации, API обязан поддерживать до 32 каналов аудио).

- Непосредственный доступ к временным и спектральным характеристикам сигнала (позволяет делать визуализации и анализ аудио потока)

- Высокоуровневое 3D распределение аудио по каналам, в зависимости от положения, направления и скорости источника звука и слушателя

- Тесная интеграция с WebRTC (как источник звука можно использовать системный микрофон, подключить гитару или микшер).

Недостатки Web Audio API:

- не предоставляет готового решения для реализации потоковой передачи аудио в реальном времени.

Учитывая все описанные выше преимущества Web Audio API, для разрабатываемого стримингового музыкального сервиса был выбран именно он. Чтобы восполнить недостаток технологии, в работе было предложена своя реализация потоковой передачи с помощью уже имеющихся в Web Audio API методов.

Для начала ознакомимся с принципом работы Web Audio API.

3.3 Порядок работы с Web Audio API

Аудио схема (Audio context)

Одним из основополагающих понятий при работе с Web Audio API является аудио контекст (audio context). Все операции со звуком в Web Audio API обрабатываются внутри него. Каждая базовая операция выполняется с аудио узлами (audio nodes), которые соединены друг с другом, образуя так называемый граф маршрутизации аудио (audio routing graph).

Существует три основных типа аудио узлов:

- источники (исходные узлы) (source node), которые генерируют свой собственный звук;

- узлы эффектов, которые изменяют звук, генерируемый другими аудио узлами;

- узлы назначения, которые воспроизводят звук или создают сетевой поток (которые посылают звук к динамикам ПК.)

Рис.8 Примеры AudioNode объектов

Каждый элемент AudioNode может иметь сколь угодно много входов/выходов потока. Потоком мы условно назвали сигнал, передающийся между двумя узлами. К любому входу можно подключить неограниченное количество потоков. Аналогично дела обстоят с выходами. Большинство элементов имеет по 1 входу и выходу. Источники сигнала не имеют входов, а выводящие элементы (узлы назначения) не имеют выходов.

Аудио узлы, связываясь между собой через входы и выходы, формируют цепь, которая начинается от одного или нескольких источников, проходит через один или несколько узлов эффектов, и заканчивается в пункте назначения (destination). Все узлы являются потомками класса AudioNode и наследуют его свойства и методы, но также могут определять и собственный функционал. Один AudioNode может принадлежать только одному AudioContext.

У одного документа может быть только один AudioContext. Этого вполне достаточно для всего спектра задач решаемого Web Audio API. Наличие одного аудио контекста позволяет строить сколь угодно сложные аудио графы с неограниченным количеством источников, получателей звукового сигнала, модулей для манипуляций со звуков и с прямыми и обратными связями между ними. Всю заботу о правильном функционировании берет на себя API. Ниже в качестве примера приведена некая абстрактная схема с двумя источниками, четырьмя промежуточными узлами фильтров и узлом назначения:

Рис.9 Абстрактная схема графа маршрутизации

Типичный рабочий процесс для веб аудио выглядит примерно так:

1) Создаем аудио контекст

2) Внутри контекста создаем источники -- такие как <audio>, осциллятор (генератора звука) или поток

3) Создаем узлы эффектов, такие как реверберация, биквадратный фильтр, паннер или компрессор

4) Выбираем пункт назначения для аудио, такой как колонки компьютера пользователя

5) Устанавливаем соединение между источниками через эффекты к пункту назначения

Рис.10 Порядок работы с Web Audio API

В Web Audio API в качестве источника сигнала могут выступать:

- OscillatorNode - генерирует сигнал с заданной частотой.

- MediaElementAudioSourceNode - используется в связке с HTML5 Audio тегами, которые выступают в качестве источников данных для него.

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

- AudioBufferSourceNode - источником данных этого элемента является AudioBuffer, который можно непосредственно создавать и заполнять каждый его канал данными в формате ИКМ (импульсно-кодовой модуляции) либо создавать из сжатых аудиоданных.

Глава 4. Анализ структуры mp3-файла и реализация потоковой передачи

4.1 Структура mp3-файла

В качестве формата потокового аудиофайла предлагается использовать MP3. Весь mp3-файл состоит из фреймов, которые в свою очередь содержат в себе заголовок и аудио данные. Рассмотрим структуру заголовка фрейма:

Рис.11 Структура заголовка mp3-фрейма

[0-10] Маркер - 11 бит, заполненные единицами (Frаme sync). Далее следуют еще три бита: версия, слой и защита от ошибок. Если принять по умолчанию что мы работаем с mp3-файлом, а не с какой-нибудь другой версией MPEG, то можно брать первые два байта как сигнатуру, в этом случае они могут иметь всего две комбинации: FF FA или FF FB.

[11-12] Индекс версии MPEG (Аudio version ID)

Рис.12 Таблица версий MPEG

[13-14] Индекс версии Lаyer (Layer index)

Рис.13 Таблица версий Layer

[15] Бит защиты (Protection bit) 1 - нет защиты 0 - заголовок защищен 16-бит. CRC (следует за заголовком)

[16-19] Индекс битрейта (Bitrate index)

Рис.14 Таблица битрейта

[20-21] Индекс частоты дискретизации (Sampling rate index)

Рис.15 Таблица частоты дискретизации

[22] Бит смещения (Padding bit). Если он устaновлен, то дaнные смещаются на 1 байт.

[23] Бит private (только для информации)

[24-25] Режим канала (Channel mode)

Рис.16 Таблица режимов канала

[26-27] Расширение режима канала (Mоde extension). Используется только с Jоint stereо

[28] Копирайт (Copyright bit) - только для информации

[29] Оригинал (Originаl bit) - только для информации.

[30-31] Акцент (Emphаsis) - в данный момент практически не используется.

4.2 Длительность композиции

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

Прежде чем подсчитывать длительность трека, необходимо определить режим сжатия. Существует 3 таких режима: 1) CBR (constant bitrate) - постоянный битрейт. Не меняется на всем протяжении трека. 2) VBR (variable bitrate) - переменный битрейт. При этом сжатии битрейт постоянно меняется на протяжении трека. 3) ABR (average bitrate) - усредненный битрейт. Это понятие используется только при кодировании файла. На «выходе» получается файл с VBR.

Переменный битрейт позволяет снизить размер файла, не снижая качества, за счет того, что на участках, где это не требуется, например тишина в начале трека, используется меньшее количество бит для кодирования. На уровне структуры файла это выражается в том, что один фрейм может иметь битрейт, например 128, а следующий может иметь уже 192, и т.д.

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

О сжатии файла с VBR сигнализирует VBR-заголовок. По его наличию можно понять, что используется переменный битрейт. Есть два вида таких заголовков: Xing и VBRI.

Xing размещается со смещением (padding) от начала первого MP3-фрейма в позиции, согласно таблице:

Рис.17 Таблица смещения заголовка XING

XING header position = MPEG first Frame header position + 4 (Bytes) + padding Если ID3-тег отсутствует, то значение MPEG first Frame header position равняется 0. В противном случае, мы должны использовать маркер фрейма, чтобы найти заголовок.

Заголовок VBRI всегда размещается со смещением +32 байта от начала первого MP3-фрейма.

Первые четыре байта в Xing заголовке содержат маркер `Xing' или `Info', а в VBRI заголовке - `VBRI'. Эти VBR заголовки имеют переменную длину и содержат различную информацию о кодировании файла.

Таким образом, мы можем проверить режим сжатия согласно следующей блок-схеме:

Рис.18 Блок-схема определения режима сжатия

Внутри VBR заголовков нас интересует информация о количестве фреймов (Number of Frames). Это число длиной 4 байта. В заголовке Xing оно содержится по смещению +8 байт от начала заголовка. В VBRI +14 байт от начала заголовка.

Следующее значение, которое нам понадобится это частота дискретизации (Sample_Frequency). Ее значение, независимо от того, переменный битрейт или постоянный, фиксированное для всего файла, и мы можем его взять из заголовка первого фрейма. Индекс частоты дискретизации хранится по смещению +20 бит от начала заголовка и занимает 2 бита. По нему мы можем найти значение частоты дискретизации согласно таблице 5.

Также необходимо получить количество сэмплов на фрейм (Sample_Number), используя следующую таблицу:

Рис.19 Таблица сэмплов на фрейм

Получив эти значения, мы сможем узнать длительность одного фрейма:

Time_Per_Frame = Sample_Number/Sample_Frequency

1) Длительность CBR файла

Для файлов, закодированных с переменным битрейтом, мы можем получить длительность трека по следующей формуле:

Длительность = Размер аудиоданных / Битрейт (в битах!) * 8

CBR Duration = File_Size*8/(Bitrate*1000);(unit of Bitrate is Kbps)

Поскольку битрейт относится только к аудиоданным и не должен учитывать данные заголовка фрейма (4 байта) при кодировании, то с помощью формулы выше мы не можем получить точную продолжительность MP3 файла. Ошибка составит примерно 1%.

Поэтому нам нужно использовать другой подход для расчета длительности аудиофайла.

Frame_Size = Frame_Header_Size + Data_Size = 4(Bytes) + Data_Size =4*8+Time_Per_Frame*Bitrate*1000(bit)

Размер фрейма можно высчитать другим способом:

Frame Size = ( (Sample_Number / 8 * Bitrate) / Sample_Frequency) + Padding Size

Или

Frame_Size = 144 * Bitrate / Sample_Frequency + Padding Size;

Total_Frame_Number = File_Size/Frame_Size;

File_Duration = Time_Per_Frame * Total_Frame_Number;

Правильно было бы под File_Size брать размер всего аудиофайла за вычетом ID3 тегов, то есть учитывать только размер фреймов. Но поскольку в сравнении со всем файлом размер тега настолько мал, что в целом им можно пренебречь и не вычитать из размера всего файла.

Файлы с CBR сжатием используют постоянный битрейт, поэтому достаточно будет проанализировать только первый фрейм, чтобы получить все необходимые для расчета длительности данные (Bitrate, Sample_Frequency).

1) Длительность VBR файла

В случае переменного битрейта можно использовать его среднее значение по всем фреймам. Для этого нужно просканировать весь файл чтобы получить все значения битрейта каждого фрейма, затем просуммировать эти значения и разделить полученное значение на общее число фреймов:

File_Duration = File_Size/Average_Bitrate

Этот метод достаточно неэффективен; нужно проанализировать целый файл и заголовки каждого фрейма, что ресурсозатратно. Рассмотрим другой подход. Поскольку, как мы убедились раннее, мы можем получить общее число фреймов и длительность одного фрейма, рассчитать длительность всего MP3 файла можно по следующей формуле:

File_Duration = Time_Per_Frame * Number_of_Frames

4.3 Реализация стримингового процесса

В данной работе предлагается возможная реализация технологии потокового вещания аудио в реальном времени на основе использования метода опережающей загрузки. Метод опережающей загрузки - это программная реализация возможности начала воспроизведения медиафайла без необходимости ожидания завершения его полного скачивания. Загрузка медиафайла состоит в передаче с сервера на клиент всего содержимого файла небольшими порциями. В процессе трансляции клиент получает поток фрагментов медиафайла длительностью несколько секунд, который ему нужно воспроизвести как цельный аудиофайл по порядку.

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

После того, как клиент получит первый фрагмент, способный к воспроизведению вместе со всеми заголовками у фреймов, он уже может начать воспроизведение аудиофайла еще до завершения полной загрузки. Параллельно с воспроизведением аудиофайла клиент производит его дозагрузку. Если каждый раз к моменту завершения воспроизведения клиентом очередного фрагмента в буфере оказывается следующий за ним фрагмент, аудиофайл будет проигрываться плавно, без рывков. Иными словами, в каждый момент времени в буфере должно содержаться достаточное количество фрагментов для обеспечения непрерывности воспроизведения. Для этого скорость загрузки должна быть не меньше скорости воспроизведения. Временная длительность фрагмента задает минимальную задержку между временем начала загрузки аудиофайла и временем начала его воспроизведения.

Далее была рассмотрена возможная схема организации потоковой передачи MP3 файла.

4.3.1 Чтение файла на сервере

Получив сигнал от клиента о начале воспроизведения, сервер открывает поток Readable для чтения из файла с использованием метода createReadStream() модуля fs. Считанные данные поток сохраняет в своем внутреннем буфере, максимальный размер которого указывается в байтах через параметр highWaterMark в конструкторе класса. По умолчанию его значение установлено в 16384 (16кБ), для нашего приложения мы используем значение, равное результату деления размера всего аудиофайла на количество фрагментов. Фрагмент кода с созданием Readable потока:

const NUM_OF_CHUNKS = 10;

filePath = path.resolve(__dirname, './private', './track2.mp3'); stat = fs.statSync(filePath); fileSize = stat.size; chunkSize = Math.floor(fileSize / NUM_OF_CHUNKS);

Листинг 1

Когда общий размер внутреннего читаемого буфера достигает порогового значения, заданного highWaterMark, стрим временно останавливает чтение данных из предустановленного ресурса, пока данные, которые на данный момент буферизируются, не будут получены в обработчике события `data'.

Открытый для чтения поток использует Event Emitter API для уведомления кода приложения, когда данные доступны для чтения из стрима. По событию `data' мы получаем порцию данных, а событие `end' сигнализирует, что все данные из источника считаны.

Чтобы корректно разделить аудиофайл необходимо в нем обнаружить фреймы. Для этого при получении очередной порции данных мы находим в ней последние 12 бит установленных в единицу, которые сигнализируют о начале последнего в полученном фрагменте фрейма. Считываем в буфер все данные до этой последней сигнатуры и отправляем их клиенту, а затем оставшимся неполным фреймом перезаписываем буфер. При получении следующего фрагмента мы соединяем имеющиеся в буфере части фрейма с новыми обнаруженными фреймами. Описанный процесс реализован в следующем примере:

let offset_ = 0; let chunkBuffer = null; readStream_.on('data', (chunk) => { offset_ = chunk.lastIndexOf(new Uint8Array([255, 251])); if (offset_ !== -1) { chunkBuffer = chunkBuffer ? Buffer.concat([chunkBuffer, chunk.slice(0, offset_)]) : chunk.slice(0, offset_); client.emit('audio', chunkBuffer); chunkBuffer = chunk.slice(offset_); offset_ = 0; } else { chunkBuffer = chunkBuffer ? Buffer.concat([chunkBuffer, chunk]) : chunk; } });

Листинг 2

4.3.2 Подходы к «склеиванию» фрагментов на клиенте

Проблема воспроизведения отдельных фрагментов аудиофайла на клиенте заключается в том, что мы не можем начать их воспроизведение, сразу как только они станут доступными, поскольку как правило при нормальной скорости Интернета небольшие фрагменты данных успевают прийти раньше чем предыдущие закончат свое воспроизведение.

Было разработано несколько вариантов организации воспроизведения отдельных фрагментов аудио данных как цельной аудиодорожки:

1) В первом подходе к решению данного вопроса учитывался тот факт, что у createBufferSource() есть onended событие, которое генерируется, когда воспроизведение буфера завершено. При наступлении указанного события мы можем передать ему callback-функцию, которая воспроизведет следующий звуковой фрагмент. Пример реализации данного подхода:

let sources = []; let isPlaying = false; const play = () => { if(sources.length === 0) { isPlaying = false; return; } let source = sources.shift(); source.onended = play; source.start(); }; audioContext.decodeAudioData(chunk) .then((audioBufferChunk) => { let source = audioContext.createBufferSource(); source.buffer = audioBufferChunk; source.connect(audioContext.destination); sources.push(source); if(!isPlaying) { isPlaying = true; play(); } });

Листинг 3

Мы создали массив объектов AudioBufferSourceNode с декодированными и готовыми к воспроизведению данными. У каждого из них в буфере содержатся данные только одного фрагмента аудиофайла. Массив пополнялся новыми объектами AudioBufferSourceNode по мере поступления новых фрагментов данных на клиент, их декодирования, создания AudioBufferSourceNode и заполнения ими его буфера.

При таком подходе проигрывание получается прерывистым, между каждым воспроизводимым аудио фрагментом возникают небольшие паузы. Чтобы оценить их наличие или отсутствие был записан звук результата воспроизведения, и полученная аудиодорожка была конвертирована в визуальное представление звуковой волны Waveform с помощью сервиса TwistedWave. На полученном изображении отчетливо были заметны паузы после проигрывания каждого аудио фрагмента. В качестве демонстрации ниже вашему вниманию представлена аудиоволна, на которой можно увидеть зазоры между всеми фрагментами песни:

Рис.20 Аудиоволна при организации воспроизведения с использованием 1го подхода

2) Другой подход заключается в планировании времени воспроизведения аудио фрагментов. Каждый кусок данных предполагается включать с определенной задержкой равной продолжительности проигрывания предыдущего фрагмента. Пример реализации данного подхода:

let nextTime = 0.01; let source = audioContext.createBufferSource(); source.buffer = audioBufferChunk; source.connect(audioContext.destination); source.start(nextTime); nextTime += audioBufferChunk.duration - 0.01;

Листинг 4

Однако данный подход также далек от идеала, поскольку во время воспроизведения образуются скачки на стыке фрагментов. В этом подходе мы не учли время, затраченное на получение данных с сервера. Чтобы решить эту проблему и правильно рассчитать задержку перед началом воспроизведения первого фрагмента необходимо сначала зафиксировать текущее время как стартовое, а затем у всех последующих приходящих частей планировать начало воспроизведения учитывая это стартовое время и прибавлять продолжительность проигрывания предыдущих фрагментов уже к этому значению. Наглядное аудио представление:

Рис.21 Аудиоволна при организации воспроизведения с использованием 2го подхода

3) Третий вариант разрабатывался с учетом недостатков двух предыдущих, а также для обеспечения непрерывного и более плавного воспроизведения было добавлено объединение буфера объекта AudioBufferSourceNode с данными из предыдущего фрагмента с буфером текущего. Это решение также упростило реализацию остановки и воссоздания воспроизведения. Пример реализации:

let sources = []; let startTime = 0; let audioBuffer = null; let nextTime = 0.01; let chunks = [];

const appendBuffer = (buffer1, buffer2) => { const numberOfChannels = Math.min(buffer1.numberOfChannels, buffer2.numberOfChannels); const tmp = audioCtx.createBuffer(numberOfChannels, (buffer1.length + buffer2.length), buffer1.sampleRate); for (let i = 0; i < numberOfChannels; i++) { const channel = tmp.getChannelData(i); channel.set(buffer1.getChannelData(i), 0); channel.set(buffer2.getChannelData(i), buffer1.length); } return tmp; };


Подобные документы

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.