Введение
Кратко о задаче: необходимо из существующего Android проекта забрать библиотеку для конвертации MIDI в mp3 и собрать ее под iOS.
После передачи мне Android проекта стал его изучать. В проекте я нашел файлы отвечающие за работу с MIDI – это 4 библиотеки:
- Fluidsynth – основная библиотека для работы с midi фалами.
- Lame-3.100 – mp3 кодировщик.
- Libsndfile – чтения и записи аудиофайлов.
- Mpg123 – аудиоплеер.
Почему не подходят библиотеки
Для Fluidsynth нет пошаговой инструкции для сборки под iOS, хотя на официальном сайте написано что кроссплатформенность поддерживается. Релевантного опыта у других я не нашел. В итоге решил отказаться от сборки fluidsynth из исходников.
Возможно я еще вернусь к этой теме, но в рамках другой статьи.
Какая есть альтернатива
Есть проект AudioKit, который полностью написан на Swift. Документация у него скудная и мне сложно было в ней разобраться. В процессе изучения понял что AudioKit под капотом использует AVFoundation
.
AudioKit задал правильный вектор и я пошел искать информацию по работе с AVFoundation
.
Работа с AVFoundation
Нашел пару тем на форуме Apple и StackOverflow:
- Compressing AVAudioPCMBuffer within AVAudioEngine Tap
- Decode AAC to PCM format using AVAudioConverter Swift
Воспроизведение MIDI
Первым делом решил попробовать воспроизвести MIDI файл в аудиоплеере. Оказывается
с этим прекрасно справляется. Ссылки на примеры не буду приводить, ищется эта информация в интернете довольно легко.AVMIDIPlayer
Еще я узнал что воспроизвести MIDI можно с помощью AVAudioEngine
и что у этого движка есть рендер и конвертер. До конвертера руки не дошли, а рендер попробовал.
Решил попробовать воспроизвести MIDI файл с помощью AVAudioEngine
, заодно поближе познакомиться.
Для начала необходимо получить ссылку на библиотеку сэмплов, инициализировать движок, объект для работы с сэмплом и секвенсор, который будет это все воспроизводить.
Пример кода:
let urlToSF2 = ...
let engine = AVAudioEngine()
let sampler = AVAudioUnitSampler()
let sequencer = AVAudioSequencer(audioEngine: self.engine)
По моему мнению, у Apple классно выражена ООП методология для работы с аудиодвижком. Очень похоже на реально использование звукового оборудования.
Пример кода:
engine.attach(sampler)
engine.connect(sampler, to: engine.mainMixerNode, format: nil)
Чтобы секвенсор мог “прочитать ноты”, их необходимо загрузить:
let options = AVMusicSequenceLoadOptions()
try sequencer.load(from: urlToMIDI, options: options)
Далее в секвенсор необходимо загрузить библиотеку сэмплов. Поддерживаются разные форматы (aupreset, DLS, SF2, EXS24):
try sampler.loadSoundBankInstrument(at: self.urlToSF2,
program: 0,
bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB),
bankLSB: UInt8(kAUSampler_DefaultBankLSB))
Осталось только запустить движок, чтобы он все это смиксовал, а секвенсор воспроизвел:
try engine.start()
try sequencer.start()
Готово. Теперь можно послушать получившуюся композицию из MIDI и сэмпла.
Интересный факт. Для macOS не обязательно загружать сэмплы – по умолчанию система будет использовать собственные.
Конвертация в ACC
Весь код из раздела воспроизведения MIDI актуален и для конвертации.
Почему ACC? Потому что обеспечивает более высокое качество звука при той же скорости передачи данных, совместим с широким спектром устройств, включая модели Apple, Nokia и Blackberry. А еще потому что MP3 был долгое время запатентован (патент на MP3 закончился в 2017 году) и Apple не могла его использовать без лицензионных отчислений.
Для целей конвертации в AVFoundation
есть метод renderOffline
который преобразует все что поступает на вход аудио движка в формат поддерживающий PCM.
Надо подготовить буфер для промежуточного сохранения аудиоданных:
let pcmFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 2)
let pcmBuffer = AVAudioPCMBuffer(pcmFormat: pcmFormat, frameCapacity: engine.manualRenderingMaximumFrameCount)
Подготовить выходной AAC аудиофайл в который будут сохранены данные:
let outputFile: AVAudioFile
let aacFormatSettings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: Int(pcmFormat.sampleRate),
AVNumberOfChannelsKey: Int(pcmFormat.channelCount),
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue
]
try outputFile = AVAudioFile(forWriting: urlToOutputACC, settings: aacFormatSettings)
Запустить цикла рендеринга. За один цикл будет преобразовано 4096 кадров аудиосэмплов:
var isRenderingEnabled = true
while isRenderingEnabled {
do {
let status = try engine.renderOffline(frameCount, to: pcmBuffer)
switch status {
case .success:
try outputFile.write(from: pcmBuffer)
case .insufficientDataFromInputNode:
throw Event.notEnoughInputDataRender
case .cannotDoInCurrentContext:
throw Event.realtimeRenderOperationError
case .error:
throw Event.noReturnedRenderedData
@unknown default:
continue
} catch {
throw Event.renderFrameError
}
}
}
После завершения цикла обработки движок и семплер необходимо остановить:
sequencer.stop()
engine.stop()
Если цикл рендеринг не остановить то выполнение будет продолжаться бесконечно. Как остановить этот цикл – пускай останется не освещенным в этой статье, будет пища для размышления.
Во время выполнения функции renderOffline
звук вы не услышите, так как оборудование (динамики, микрофон) отключается от аудиодвижка. Более подробно можно почитать в статье – https://developer.apple.com/documentation/avfaudio/performing-offline-audio-processing
Глоссарий терминов:
- MIDI включает в себя ноты, тайминги и информацию о высоте тона, которые принимающее устройство использует для воспроизведения музыки из собственной звуковой библиотеки.
- Относительно небольшой оцифрованный звуковой фрагмент. В качестве семпла чаще выступает звук акустического инструмента (например, рояля Steinway, литавр, флейты и тому подобных)
- PCM – Pulse Code Modulation.
- ASBD – audio stream basic description, для указания PCM формата.
- CBR – constant bit rate формат, в котором каналы имеют одинаковый размер.
- VBR – variable bit rate формат, в котором каналы имеют неодинаковые размеры.
- Audio stream (аудиопоток) – это непрерывная серия данных, представляющая собой звук, например песню.
- Сhannel (каналы) – это дискретная дорожка монофонического звука. Монофонический поток имеет один канал, стереопоток – два канала.
- Sample (сэмпл) – это одно числовое значение для одного аудиоканала в аудиопотоке.
- Frame (кадр) – это набор совпадающих по времени сэмплов. Например, в линейном PCM – стереофоническом файле на один кадр приходится два сэмпла: один для левого канала и один для правого.
- Packet (пакет) – это набор из одного или нескольких последовательных кадров. Пакет определяет наименьший значимый набор кадров для данного формата аудиоданных и является наименьшей единицей данных, для которой можно измерить время. В линейном PCM-аудио пакет содержит один кадр. В сжатых форматах он обычно содержит больше кадров. В некоторых форматах количество кадров в пакете варьируется.
- Sample rate (частота дискретизации потока) – это количество кадров в секунду в несжатом аудио или, для сжатых форматов, эквивалент в декомпрессированном аудио.
Ссылки на источники:
- Тема на форуме Apple – https://forums.developer.apple.com/forums/thread/763362
- Тема на StackOverflow – https://stackoverflow.com/questions/51360127/decode-aac-to-pcm-format-using-avaudioconverter-swift
- Статья по рендерингу из официальной документации Apple – https://developer.apple.com/documentation/avfaudio/performing-offline-audio-processing
Добавить комментарий