音声ファイル内に歌や楽器の演奏が含まれているかをSound Analysisを使用して手軽に判別してみた

はじめに

はじめまして。株式会社Voicy iOSエンジニアの立花(@kzytcbn315)です。

先日、国内最大級のeスポーツイベントを開催するRAGEが開催するShadowverseというゲームの大会に参加して賞金を獲得しました。 ゲームをやってお金も貰える、なんともいい時代になったもんだなとしみじみと思いました。

前置きが長くなりましたが、ここからが本題です。

昨今、AIの創作物の著作権について議論されることが増えたと思いますが、音声コンテンツも著作権の侵害には気をつけなくてはなりません。

音を扱うコンテンツとして代表的なのは音楽だと思いますが、うっかり音楽が音声コンテンツに紛れ込んでしまった場合に著作権を侵害してしまう恐れがあります。

そこでふと、音声ファイルの中に音楽が含まれているのを手軽に検知する方法がないかな、と考えたのが今回の記事を書くきっかけになりました。

ShazamKit

音声ファイルの中に音楽が含まれているかを検知する方法として、パッと思いつくのはShazamKitです。

ShazamKitはiOS15+の音声認識用のフレームワークです。

ShazamKit(またはShazamのアプリ)を使用したことがある方ならご存知だとは思いますが、ShazamKitの精度は手軽に使えるのに非常に高いです。

しかし、結論を言うとShazamKitを利用してもやりたいことは実現できないです。

ShazamKitは音声認識用のフレームワークなので、特定の音源を検知することは可能ですが、不特定多数の音源を認識することはできません。

Aという楽曲のオリジナル音源は特定することはできます。

ですが、Aという楽曲のカバー音源であったり、カラオケで歌った曲などは認識することができません。*1

これだと、Shazamに登録されているカタログ以外を検知できず不完全なものになってしまいます。

Sound Analysis

ShazamKitと同じような音声を扱うライブラリに、Sound Analysisがあります。

Sound Analysisは、iOS13以降から提供されている音声分類用のフレームワークです。

iOS14まではCore MLのモデルを自分で作成しなくてはならなかったのですが、iOS15からは300種類以上の音声を分類するためのモデルが提供されたことによってSoundAnalysisを使用するハードルが大きく下がりました。

こちらのSound AnalysisはShazamKitと違い、特定の音源を認識することはできないのですが、不特定多数の音声を分類することができます。

Aという楽曲のオリジナル音源やカバー音源を認識することはできないが、Aという楽曲のオリジナル音源やカバー音源が音楽であると分類することができます。

Sound Analysisの音声分類を使用することで音声ファイル内に音楽、歌、楽器の演奏などが含まれているかを判別することができます。

分類できるクラスの一覧の確認方法

現状では303種類の音声を分類することができます。実際に分類できるクラスの一覧を確認したい場合は以下で取得できます。

do {
    let request = try SNClassifySoundRequest(classifierIdentifier: .version1)
    // 分類できる音声の一覧
    print(request.knownClassifications)
} catch {
    // エラー処理
}

Sound Analysisで分類可能なクラス一覧

SoundAnalysisで分類可能なクラスの一覧

分類できるクラスの一覧の中から音楽周りを抜粋

303種類の中から音楽周りのクラスは76種類でした。*2

知らない楽器が結構あったりしたので、主要なものや目的を絞ればここまでは必要ないと思います。

["singing", "choir_singing", "yodeling", "rapping", "humming", "whistling", "music", "plucked_string_instrument", "guitar", "electric_guitar",
 "bass_guitar", "acoustic_guitar", "steel_guitar_slide_guitar", "guitar_tapping", "guitar_strum", "banjo", "sitar", "mandolin", "zither", "ukulele",
 "keyboard_musical", "piano", "electric_piano", "organ", "electronic_organ", "hammond_organ", "synthesizer", "harpsichord", "percussion", "drum_kit",
 "drum", "snare_drum", "bass_drum", "timpani", "tabla", "cymbal", "hi_hat", "tambourine", "rattle_instrument", "gong",
 "mallet_percussion", "marimba_xylophone", "glockenspiel", "vibraphone", "steelpan", "orchestra", "brass_instrument", "french_horn", "trumpet",
 "trombone", "bowed_string_instrument", "violin_fiddle", "cello", "double_bass", "wind_instrument", "flute", "saxophone", "clarinet", "oboe",
 "bassoon", "harp", "bell", "church_bell", "bicycle_bell", "cowbell", "tuning_fork", "chime", "wind_chime", "harmonica",
 "accordion", "bagpipes", "didgeridoo", "shofar", "theremin", "singing_bowl", "disc_scratching"]

音声ファイル内に歌や楽器が含まれているか判別する

ここからは実際に音声ファイル内に歌や楽器が含まれているかを検知する方法を説明していきます。

登場人物

  • 判別する音声ファイル
  • SNClassifySoundRequest
  • SNAudioFileAnalyzer
  • SNResultsObserving
  • SNClassificationResult

判別する音声ファイル

当たり前ですが判別する音声ファイルを用意する必要があります。*3

SNClassifySoundRequest

func makeRequest() -> SNClassifySoundRequest {
    // version1というモデルを渡している(現状はversion1しかモデルがない)
    let request = try! SNClassifySoundRequest(classifierIdentifier: .version1)
    // 
    request.windowDuration = CMTime(seconds: 10, preferredTimescale: 10)
    return request
}

SNClassifySoundRequestに音を分類するためのモデルを設定します。

windowDurationを指定することで、分類に使用する音声ファイルのバッファの長さを指定できます。

SNAudioFileAnalyzer

// 分類する音声ファイルを指定
let analyzer = try! SNAudioFileAnalyzer(url: fileURL)
// 分類結果を受け取る処理
let observer = ResultsObserver()
observer.delegate = delegate
// モデルを指定して分類を行う
try! analyzer.add(makeRequest(), withObserver: observer)
analyzer.analyze()

SNAudioFileAnalyzerにモデルと音声ファイルを渡すことで、音声ファイル内の音を分類します。

SNResultsObserving

enum Classifications {
    case humming
    case music
    case guitar
    
    var name: String {
        switch self {
        case .humming:
            return "ハミング"
        case .music:
            return "音楽"
        case .guitar:
            return "ギター"
        }
    }
}

class ResultsObserver: NSObject, SNResultsObserving {

    func request(_ request: SNRequest, didProduce result: SNResult) {
        if let result = result as? SNClassificationResult {
            result.classifications.forEach {
                // 信頼度が0.8より上のみ検知したと判断
                if $0.confidence > 0.8 {
                    if let classifications = Classifieres(rawValue: $0.identifier) {
                        // 分類されたクラス名
                        print(classifications.name)
                        // 信頼度
                        print($0.confidence)
                    }
                }
            }
        }
    }
}

SNResultsObservingで分類の結果を受け取る。

result.classifications

予測(identifier)とその予測の信頼度(confidence)がペアになっています。

予測の信頼度は低すぎると誤検知につながり、高すぎると検知しきれない可能性があるのでプロダクトに合った値を見つける必要があると思います。

まとめ

SoundAnalysisを使用することで音声ファイル内の歌や楽器の演奏をお手軽に検知することができます。

ShazamKitと違い特定の音声を認識することはできないですが、汎用的に音楽が含まれているかを判別するだけであれば事足りるなと思いました。

参考にしたサイト

*1:厳密に言うとカバー音源でもShazamのカタログに登録されていたり、自身でカタログを作成することで認識することはできます。

*2:目検なので漏れありそう

*3:AudioEngineなどを使用したStreamの音声を使うこともできますが今回は説明を割愛