iosswiftshazam

ShazamKit: music detection "session.match(signature)" with no match found and no error was thrown, weird


Today, I create a small app to try ShazamKit music detection ability in iOS 15. Follow a tutorial on Youtube, and I have Apple developer membership and have enabled the ShazamKit service for this app identifier.

In short, I want to detect a song metadata with ShazamKit from the audio file inside app.

The problem is that both of delegate method didFind and didNotFindMatchFor didn't fire though I have generated the signature successfully. I think it should give me an error in didNotFindMatchFor delegate method if there is no match found at least, but it doesn't.

It's a pretty new feature, there is not that much related stuff I could find. Appreciate for any help.

More info: I do find some stuff using audioEngine, however that use output from Microphone, if user listen music with a headphone, that would be not possible. In my case I want to use the file itself since my production app is a music player, which stores a lot audio files in sandbox.

import ShazamKit
import UIKit

class ViewController: UIViewController {
    
    lazy var recoButton: UIButton = {
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 120, height: 60))
        button.layer.cornerRadius = 8
        button.backgroundColor = .brown
        button.setTitle("Recognize", for: .normal)
        button.addTarget(self, action: #selector(recognizeSong), for: .touchUpInside)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(recoButton)
        recoButton.center = view.center
    }
    
    @objc func recognizeSong(_ sender: UIButton) {
        print("reco button tapped")
        // ShazamKit is available from iOS 15
        if #available(iOS 15, *) {
            // session
            let session = SHSession()

            // delegate
            session.delegate = self
            do {
                // get track
                guard let url = Bundle.main.url(forResource: "Baby One More Time", withExtension: "mp3") else {
                    print("url is NULLLL")
                    return }
                // create audio file
                let file = try AVAudioFile(forReading: url)
                let frameCapacity = AVAudioFrameCount(file.length / 26)
                // Audio -> Buffer
                guard let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: frameCapacity) else {
                    print("Failed to create a buffer")
                    return }
                // Read file into buffer
                try file.read(into: buffer)
                // SignatureGenerator
                let generator = SHSignatureGenerator()
                try generator.append(buffer, at: nil)
                // create signature
                let signature = generator.signature()
                // try to match
                session.match(signature)
            } catch {
                print(error)
            }
        } else {
            // unavailable alert
        }
    }
}

extension ViewController: SHSessionDelegate {
    func session(_ session: SHSession, didFind match: SHMatch) {
        print("Match found!")

        // get results
        let items = match.mediaItems
        items.forEach { item in
            print(item.title ?? "title")
            print(item.artist ?? "artist")
            print(item.artworkURL?.absoluteURL ?? "artwork url")
        }
    }

    func session(_ session: SHSession, didNotFindMatchFor signature: SHSignature, error: Error?) {
        if let error = error {
            print(error)
        }
    }
}

Solution

  • Per today's test & observation. I found that we need to convert input audio format to AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1) with a built-in converter(AVAudioConverter). Then create the output buffer, and the music is recognized this time.

    I pick 10+ music files for a test run, all of them could be detected except one. And the interesting thing is this music file could be detected by Shazam app, I have no idea what is the reason as there is no error is shown for the un-detected music song.

    Anyway, now it is worked. Update code as below, it is just a combination of several functions for test purpose, you should separate them into different functions for production.

        @objc func recognizeSong(_ sender: UIButton) {
            print("reco button tapped")
            // ShazamKit is available from iOS 15
            if #available(iOS 15, *) {
                // session
                let session = SHSession()
                session.delegate = self
                
                guard let url = Bundle.main.url(forResource: "You Belong With Me", withExtension: "mp3") else {
                    return
                }
                
                guard let audioFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1) else {
                    return
                }
                
                let generator = SHSignatureGenerator()
                
                do {
                    
                    let audioFile = try AVAudioFile(forReading: url)
                    
                    guard let inputBuffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: 44100 * 10),
                          let outputBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: 44100 * 10) else {
                              return
                          }
                    // Read file into buffer
                    let inputBlock : AVAudioConverterInputBlock = { inNumPackets, outStatus in
                        do {
                            try audioFile.read(into: inputBuffer)
                            outStatus.pointee = .haveData
                            return inputBuffer
                        } catch {
                            if audioFile.framePosition >= audioFile.length {
                                outStatus.pointee = .endOfStream
                                return nil
                            } else {
                                outStatus.pointee = .noDataNow
                                return nil
                            }
                        }
                    }
                    
                    guard let converter = AVAudioConverter(from: audioFile.processingFormat, to: audioFormat) else {
                        return
                    }
                    
                    let status = converter.convert(to: outputBuffer, error: nil, withInputFrom: inputBlock)
                    if status == .error || status == .endOfStream {
                        return
                    }
                    
                    try generator.append(outputBuffer, at: nil)
                    
                    if status == .inputRanDry {
                        return
                    }
                } catch {
                    print(error)
                }
                
                // create signature
                let signature = generator.signature()
                // try to match
                session.match(signature)
            } else {
                // unavailable alert
            }
        }
    }
    

    Reference: Apple forums