Как конвертировать MIDI в ACC на iOS

Введение

Кратко о задаче: необходимо из существующего Android проекта забрать библиотеку для конвертации MIDI в mp3 и собрать ее под iOS.

После передачи мне Android проекта стал его изучать. В проекте я нашел файлы отвечающие за работу с MIDI – это 4 библиотеки:

  1. Fluidsynth – основная библиотека для работы с midi фалами.
  2. Lame-3.100 – mp3 кодировщик.
  3. Libsndfile – чтения и записи аудиофайлов.
  4. Mpg123 – аудиоплеер.

Почему не подходят библиотеки

Для Fluidsynth нет пошаговой инструкции для сборки под iOS, хотя на официальном сайте написано что кроссплатформенность поддерживается. Релевантного опыта у других я не нашел. В итоге решил отказаться от сборки fluidsynth из исходников.

Возможно я еще вернусь к этой теме, но в рамках другой статьи.

Какая есть альтернатива

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

AudioKit задал правильный вектор и я пошел искать информацию по работе с AVFoundation.

Работа с AVFoundation

Нашел пару тем на форуме Apple и StackOverflow:

Воспроизведение 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 (частота дискретизации потока) – это количество кадров в секунду в несжатом аудио или, для сжатых форматов, эквивалент в декомпрессированном аудио.

Ссылки на источники:

  1. Тема на форуме Apple – https://forums.developer.apple.com/forums/thread/763362
  2. Тема на StackOverflow – https://stackoverflow.com/questions/51360127/decode-aac-to-pcm-format-using-avaudioconverter-swift
  3. Статья по рендерингу из официальной документации Apple – https://developer.apple.com/documentation/avfaudio/performing-offline-audio-processing

Опубликовано

в

,

от

Метки:

Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *