Mercurial > hg > ofa-vamp-plugin
changeset 1:150ad38c1871 ofa-vamp-plugin
* First revision of MusicIP OFA fingerprinting and PUID lookup plugin
| author | cannam |
|---|---|
| date | Mon, 24 Sep 2007 13:36:27 +0000 |
| parents | e70d9b1aa0a7 |
| children | 8056c637875b |
| files | Makefile OfaVampPlugin.cpp OfaVampPlugin.h ofa-vamp-plugin.cat protocol.cpp protocol.h |
| diffstat | 6 files changed, 750 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Mon Sep 24 13:36:27 2007 +0000 @@ -0,0 +1,11 @@ + +CXXFLAGS := -I../vamp-plugin-sdk -O3 -Wall -march=pentium4 -msse -msse2 -fomit-frame-pointer -ffast-math + +ofa-vamp-plugin.so: OfaVampPlugin.o protocol.o + g++ -shared $^ -o $@ -L../vamp-plugin-sdk/vamp-sdk -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic -lofa -lcurl -lexpat + +clean: + rm *.o + +OfaVampPlugin.o: OfaVampPlugin.h protocol.h +protocol.o: protocol.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OfaVampPlugin.cpp Mon Sep 24 13:36:27 2007 +0000 @@ -0,0 +1,293 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "OfaVampPlugin.h" + +#include <vamp/vamp.h> +#include <vamp-sdk/PluginAdapter.h> +#include <vamp-sdk/RealTime.h> + +#include <cstdlib> +#include <cmath> + +#include <ofa1/ofa.h> + +#include <endian.h> + +#include "protocol.h" + +using std::string; + +OfaVampPlugin::OfaVampPlugin(float inputSampleRate) : + Plugin(inputSampleRate), + m_buffer(0), + m_bufsiz(0), + m_bufidx(0), + m_channels(0), + m_blockSize(0), + m_totalCount(0), + m_enough(false) +{ +} + +OfaVampPlugin::~OfaVampPlugin() +{ + if (m_buffer) free(m_buffer); +} + +string +OfaVampPlugin::getIdentifier() const +{ + return "ofa"; +} + +string +OfaVampPlugin::getName() const +{ + return "MusicIP OFA Audio Fingerprinter"; +} + +string +OfaVampPlugin::getDescription() const +{ + return "Calculates an audio fingerprint using the MusicIP fingerprinting library"; +} + +string +OfaVampPlugin::getMaker() const +{ + return "Chris Cannam"; +} + +int +OfaVampPlugin::getPluginVersion() const +{ + return 1; +} + +string +OfaVampPlugin::getCopyright() const +{ + return ""; //!!! +} + +OfaVampPlugin::ParameterList +OfaVampPlugin::getParameterDescriptors() const +{ + ParameterList list; + + return list; +} + +float +OfaVampPlugin::getParameter(std::string name) const +{ + return 0.0; +} + +void +OfaVampPlugin::setParameter(std::string name, float value) +{ +} + +size_t +OfaVampPlugin::getPreferredStepSize() const +{ + return 0; +} + +size_t +OfaVampPlugin::getPreferredBlockSize() const +{ + return 0; +} + +bool +OfaVampPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (stepSize != blockSize) { + std::cerr << "ERROR: OfaVampPlugin::initialise: stepSize must be equal to blockSize (" << stepSize << " != " << blockSize << ")" << std::endl; + return false; + } + + m_bufsiz = 1048576; + m_buffer = (int16_t *)malloc(m_bufsiz * sizeof(int16_t)); + m_bufidx = 0; + m_channels = channels; + m_blockSize = blockSize; + m_totalCount = 0; + m_enough = false; + return true; +} + +void +OfaVampPlugin::reset() +{ + m_bufidx = 0; + m_totalCount = 0; + m_print = ""; + m_enough = false; +} + +OfaVampPlugin::OutputList +OfaVampPlugin::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor desc; + desc.identifier = "fingerprint"; + desc.name = "Fingerprint"; + desc.description = "Single result containing the audio fingerprint as its label"; + desc.unit = ""; + desc.hasFixedBinCount = true; + desc.binCount = 0; + desc.hasKnownExtents = false; + desc.isQuantized = false; + desc.sampleType = OutputDescriptor::VariableSampleRate; + desc.sampleRate = m_inputSampleRate; + list.push_back(desc); + + desc.identifier = "puid"; + desc.name = "PUID"; + desc.description = "Single result containing the MusicIP online PUID for this audio track, if available"; + desc.unit = ""; + desc.hasFixedBinCount = true; + desc.binCount = 0; + desc.hasKnownExtents = false; + desc.isQuantized = false; + desc.sampleType = OutputDescriptor::VariableSampleRate; + desc.sampleRate = m_inputSampleRate; + list.push_back(desc); + + return list; +} + +// client id: a008bd70c9d3c681e2ff336e3ceaa6dc (limited) + +OfaVampPlugin::FeatureSet +OfaVampPlugin::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + m_totalCount += m_blockSize; + + if (m_enough) return FeatureSet(); + + // libofa requires the first 135 seconds of audio (where available) + size_t reqd = lrintf(ceil(135.f * m_inputSampleRate)); + + size_t effectiveChannels = m_channels; + if (effectiveChannels > 2) effectiveChannels = 2; + + if (m_bufidx + m_blockSize * effectiveChannels > m_bufsiz) { + m_bufsiz *= 2; + m_buffer = (int16_t *)realloc(m_buffer, m_bufsiz * sizeof(int16_t)); + std::cerr << "realloc " << m_bufsiz/2 << " -> " << m_bufsiz << std::endl; + } + + for (size_t i = 0; i < m_blockSize; ++i) { + for (size_t c = 0; c < effectiveChannels; ++c) { + m_buffer[m_bufidx + i * effectiveChannels + c] = + int16_t(inputBuffers[c][i] * 32767.0); + } + } + + m_bufidx += m_blockSize * effectiveChannels; + + if (m_bufidx >= reqd * effectiveChannels) { + + m_enough = true; + + FeatureSet fset; + + int endian = OFA_LITTLE_ENDIAN; + +#ifdef __BYTE_ORDER +#if __BYTE_ORDER == _BIG_ENDIAN + endian = OFA_BIG_ENDIAN; +#endif +#endif + + const char *print = ofa_create_print + ((unsigned char *)m_buffer, + endian, + m_bufidx, + lrintf(m_inputSampleRate), + m_channels > 1); + + if (!print) { + std::cerr << "OfaVampPlugin: ERROR from libofa" << std::endl; + return fset; + } + + m_print = print; + + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = Vamp::RealTime::zeroTime; + feature.label = m_print; + fset[0].push_back(feature); + return fset; + } + + return FeatureSet(); +} + +OfaVampPlugin::FeatureSet +OfaVampPlugin::getRemainingFeatures() +{ + FeatureSet fset; + + if (m_print == "") return fset; + + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = Vamp::RealTime::zeroTime; + + int endian = OFA_LITTLE_ENDIAN; + +#ifdef __BYTE_ORDER +#if __BYTE_ORDER == _BIG_ENDIAN + endian = OFA_BIG_ENDIAN; +#endif +#endif + + AudioData data; + data.setData + (0, + endian, + m_bufidx, + lrintf(m_inputSampleRate), + m_channels > 1, + lrintf((m_totalCount * 1000.f) / m_inputSampleRate), //!!! approx + "wav"); //!!! + + data.info.setPrint(m_print); + + TrackInformation *info = data.getMetadata + ("a008bd70c9d3c681e2ff336e3ceaa6dc", + "1", + false); + + if (!info) { + std::cerr << "OfaVampPlugin: ERROR from ofa/protocol, unable to retrieve PUID for fingerprint" << std::endl; + std::cerr << "Fingerprint was: " << m_print << std::endl; + } else { + feature.label = info->getPUID(); + fset[1].push_back(feature); + } + + return fset; +} + +static Vamp::PluginAdapter<OfaVampPlugin> ofaAdapter; + +const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version, + unsigned int index) +{ + if (version < 1) return 0; + + switch (index) { + case 0: return ofaAdapter.getDescriptor(); + default: return 0; + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OfaVampPlugin.h Mon Sep 24 13:36:27 2007 +0000 @@ -0,0 +1,57 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#ifndef _OFA_VAMP_PLUGIN_H_ +#define _OFA_VAMP_PLUGIN_H_ + +#include <vamp-sdk/Plugin.h> + +#include <pthread.h> + +class OfaVampPlugin : public Vamp::Plugin +{ +public: + OfaVampPlugin(float inputSampleRate); + virtual ~OfaVampPlugin(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + size_t getMinChannelCount() const { return 1; } + size_t getMaxChannelCount() const { return 2; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string) const; + void setParameter(std::string, float); + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + int16_t *m_buffer; + size_t m_bufsiz; + size_t m_bufidx; + size_t m_channels; + size_t m_blockSize; + size_t m_totalCount; + std::string m_print; + bool m_enough; +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ofa-vamp-plugin.cat Mon Sep 24 13:36:27 2007 +0000 @@ -0,0 +1,1 @@ +vamp:ofa-vamp-plugin:ofa::Fingerprinting
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/protocol.cpp Mon Sep 24 13:36:27 2007 +0000 @@ -0,0 +1,274 @@ +/* ------------------------------------------------------------------ + + libofa -- the Open Fingerprint Architecture library + + Public Domain (PD) 2006 MusicIP Corporation + No rights reserved. + +-------------------------------------------------------------------*/ +#include <stdio.h> +#include <stdlib.h> +#include <string> +#include <map> +#include <expat.h> +#include <curl/curl.h> +#include <curl/types.h> +#include <curl/easy.h> + +using namespace std; + +#include "protocol.h" + +const char *url = "http://ofa.musicdns.org/ofa/1/track"; +const char *userAgent = "libofa_example"; +const char *unknown = "unknown"; + +// Lookup by fingerprint +const char *request_format = + "cid=%s&" // Client ID + "cvr=%s&" // Client Version + "fpt=%s&" // Fingerprint + "rmd=%d&" // m = 1: return metadata; m = 0: only return id + "brt=%d&" // bitrate (kbps) + "fmt=%s&" // File extension (e.g. mp3, ogg, flac) + "dur=%ld&" // Length of track (milliseconds) + "art=%s&" // Artist name. If there is none, send "unknown" + "ttl=%s&" // Track title. If there is none, send "unknown" + "alb=%s&" // Album name. If there is none, send "unknown" + "tnm=%d&" // Track number in album. If there is none, send "0" + "gnr=%s&" // Genre. If there is none, send "unknown" + "yrr=%s&" // Year. If there is none, send "0" + "enc=%s&" // Encoding. e = true: ISO-8859-15; e = false: UTF-8 (default). Optional. + "\r\n"; + +// Lookup by PUID (Most fields drop out) +const char *request_format2 = + "cid=%s&" // Client ID + "cvr=%s&" // Client Version + "pid=%s&" // PUID + "rmd=%d&" // m = 1: return metadata; m = 0: only return id + "brt=%d&" // bitrate (kbps) + "fmt=%s&" // File extension (e.g. mp3, ogg, flac) + "dur=%ld&" // Length of track (milliseconds) + "art=%s&" // Artist name. If there is none, send "unknown" + "ttl=%s&" // Track title. If there is none, send "unknown" + "alb=%s&" // Album name. If there is none, send "unknown" + "tnm=%d&" // Track number in album. If there is none, send "0" + "gnr=%s&" // Genre. If there is none, send "unknown" + "yrr=%s&" // Year. If there is none, send "0" + "enc=%s&" // Encoding. e = true: ISO-8859-15; e = false: UTF-8 (default). Optional. + "\r\n"; + + +// -------------------------------------------------------------------- +// HTTP POST support using standard curl calls +// -------------------------------------------------------------------- +size_t data_callback(void *ptr, size_t size, size_t num, void *arg) +{ + string *str = (string *)arg; + (*str) += string((const char *)ptr, size * num); + return size * num; +} + +long http_post(const string &url, const string &userAgent, const string &postData, string &doc) +{ + CURL *curl; + long ret = 0; + struct curl_slist *headerlist=NULL; + + headerlist = curl_slist_append(headerlist, "Expect:"); + + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&doc); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, data_callback); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postData.length()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str()); + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &ret); + curl_easy_cleanup(curl); + + curl_slist_free_all (headerlist); + + return ret; +} + +// -------------------------------------------------------------------- +// XML Parsing support +// -------------------------------------------------------------------- + +struct ParseInfo +{ + string path; + string pcdata; + TrackInformation *info; +}; + +void begin_element(void *data, const XML_Char *el, const XML_Char **attr) +{ + map<string, string> attrs; + + for(; *attr;) { + string key = string((char *)*(attr++)); + string value = string((char *)*(attr++)); + attrs[key] = value; + } + + ((ParseInfo *)data)->path += string("/") + string(el); + if (((ParseInfo *)data)->path == "/metadata/track/puid-list/puid") + ((ParseInfo *)data)->info->setPUID(attrs["id"]); + + ((ParseInfo *)data)->pcdata = ""; +} + +void end_element(void *data, const XML_Char *el) +{ + string::size_type pos; + + if (((ParseInfo *)data)->path == "/metadata/track/title") + ((ParseInfo *)data)->info->setTrack(((ParseInfo *)data)->pcdata); + if (((ParseInfo *)data)->path == "/metadata/track/artist/name") + ((ParseInfo *)data)->info->setArtist(((ParseInfo *)data)->pcdata); + + pos = ((ParseInfo *)data)->path.rfind("/"); + if (pos != string::npos) + ((ParseInfo *)data)->path = ((ParseInfo *)data)->path.substr(0, pos); +} + +void pc_data(void *data, const XML_Char *charData, int len) +{ + char *temp; + + temp = new char[len + 1]; + strncpy(temp, (char *)charData, len); + temp[len] = 0; + ((ParseInfo *)data)->pcdata += string(temp); + delete temp; +} + +bool parse_xml(const string &doc, TrackInformation *info, string &err) +{ + ParseInfo pinfo; + + err = ""; + pinfo.info = info; + XML_Parser parser = XML_ParserCreate(NULL); + XML_SetUserData(parser, (void *)&pinfo); + XML_SetElementHandler(parser, ::begin_element, ::end_element); + XML_SetCharacterDataHandler(parser, ::pc_data); + int ret = XML_Parse(parser, doc.c_str(), doc.length(), 1); + + if (ret) + { + XML_ParserFree(parser); + return true; + } + + err = string(XML_ErrorString(XML_GetErrorCode(parser))); + char num[10]; + sprintf(num, "%d", XML_GetCurrentLineNumber(parser)); + err += string(" on line ") + string(num); + XML_ParserFree(parser); + + return false; +} + +// -------------------------------------------------------------------- +// Retrieve metadata for fingerprint +// -------------------------------------------------------------------- + +// Returns true on success +bool retrieve_metadata(string client_key, string client_version, + TrackInformation *info, bool getMetadata) +{ + if (!info) + return false; + + // All metadata fields must be provided before this call if the + // information is available, as part of the Terms of Service. + // This helps create a better database for all users of the system. + // + // If the fields are not available, you can use default values. + // Here we check for fields which have no default values. + if (client_key.length() == 0) + return false; + if (client_version.length() == 0) + return false; + + bool lookupByPrint = false; + if (info->getPUID().length() == 0) { + // Lookup by fingerprint + if (info->getPrint().length() == 0) + return false; + if (info->getFormat().length() == 0) + return false; + if (info->getLengthInMS() == 0) + return false; + + lookupByPrint = true; + } + + // Sloppily estimate the size of the resultant URL. Err on the side of making the string too big. + int bufSize = strlen(lookupByPrint ? request_format : request_format2) + + client_key.length() + client_version.length() + + (lookupByPrint ? info->getPrint().length() : info->getPUID().length()) + + 16 + // getMetadata ? 1 : 0, + 16 + // info->getBitrate(), + 16 + //info->getFormat().c_str(), + 16 + //info->getLengthInMS(), + ((info->getArtist().c_str() == 0) ? strlen(unknown) : info->getArtist().length()) + + ((info->getTrack().c_str() == 0) ? strlen(unknown) : info->getTrack().length()) + + ((info->getAlbum().c_str() == 0) ? strlen(unknown) : info->getAlbum().length()) + + 16 + // info->getTrackNum() + + ((info->getGenre().c_str() == 0) ? strlen(unknown) : info->getGenre().length()) + + ((info->getYear().c_str() == 0) ? 1 : info->getYear().length()) + + info->getEncoding().length(); + + char *buf = new char[bufSize]; + sprintf(buf, lookupByPrint ? request_format : request_format2, + client_key.c_str(), + client_version.c_str(), + lookupByPrint ? info->getPrint().c_str() : info->getPUID().c_str(), + getMetadata ? 1 : 0, + info->getBitrate(), + info->getFormat().c_str(), + info->getLengthInMS(), + (info->getArtist().length() == 0) ? unknown : info->getArtist().c_str(), + (info->getTrack().length() == 0) ? unknown : info->getTrack().c_str(), + (info->getAlbum().length() == 0) ? unknown : info->getAlbum().c_str(), + info->getTrackNum(), + (info->getGenre().length() == 0) ? unknown : info->getGenre().c_str(), + (info->getYear().length() == 0) ? "0" : info->getYear().c_str(), + info->getEncoding().c_str()); + + string response; +// printf("request: '%s'\n", buf); + long ret = http_post(url, userAgent, buf, response); + delete [] buf; + + if (ret != 200) + { +// printf("Error: %ld\n", ret); +// printf("response: %s\n\n", response.c_str()); + return false; + } +// printf("response: %s\n\n", response.c_str()); + + unsigned int q = response.find("<?xml"); + if (q != string::npos) { + response = response.substr(q); + } + string err; + if (!parse_xml(response, info, err)) { + // Clears title if it wasn't returned + info->setTrack(""); + + // Clears artists if it wasn't returned + info->setArtist(""); + } + return true; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/protocol.h Mon Sep 24 13:36:27 2007 +0000 @@ -0,0 +1,114 @@ +/* ------------------------------------------------------------------ + + libofa -- the Open Fingerprint Architecture library + + Public Domain (PD) 2006 MusicIP Corporation + No rights reserved. + +-------------------------------------------------------------------*/ +#ifndef __PROTOCOL_H__ +#define __PROTOCOL_H__ + +#include <string> +#include "ofa1/ofa.h" + +using namespace std; + +// This object must be filled out completely prior to making any +// calls to the server. On return, some fields will be filled out. +class TrackInformation { +private: + string puid; + string print; + string encoding; // All other strings must honor this encoding + int bitrate; // i.e. "192kbps", use 0 for VBR or freeformat + string format; // File extension + long length_in_ms; // In milliseconds + string artist; + string track; + string album; + int trackNum; // use 0 if not known + string genre; + string year; +public: + TrackInformation() : + bitrate(0), length_in_ms(0), trackNum(0) {} + ~TrackInformation() {} + void setPrint(string p) { print = p; } + string getPrint() const { return print; } + // Only supported encodings are UTF-8 (default) and ISO-8859-15 + void setEncoding(string e) { encoding = e; } + string getEncoding() const { return encoding; } + void setBitrate(int b) { bitrate = b; } + int getBitrate() const { return bitrate; } + void setFormat(string fmt) { format = fmt; } + string getFormat() const { return format; } + void setLengthInMS(long ms) { length_in_ms = ms; } + long getLengthInMS() const { return length_in_ms; } + void setArtist(string name) { artist = name; } + string getArtist() const { return artist; } + void setTrack(string name) { track = name; } + string getTrack() const { return track; } + void setAlbum(string name) { album = name; } + string getAlbum() const { return album; } + void setTrackNum(int t) { trackNum = t; } + int getTrackNum() const { return trackNum; } + void setGenre(string g) { genre = g; } + string getGenre() const { return genre; } + void setYear(string y) { year = y; } + string getYear() const { return year; } + void setPUID(string id) { puid = id; } + string getPUID() const { return puid; } +}; + +// Get your unique key at http://www.musicdns.org +bool retrieve_metadata(string client_key, string client_verstion, + TrackInformation *info, bool getMetadata); + +class AudioData { +private: + unsigned char *samples; + int byteOrder; + long size; + int sRate; + bool stereo; +public: + TrackInformation info; + AudioData() : samples(0), size(0), sRate(0), stereo(false) {} + ~AudioData() { + delete[] samples; + } + // size is number of samples (half the number of bytes) + void setData(unsigned char*_samples, int _byteOrder, long _size, + int _sRate, bool _stereo, int _ms, string _fmt) { + samples = _samples; + byteOrder = _byteOrder; + size = _size; + sRate = _sRate; + stereo = _stereo; + // These two fields are used later for the protocol layer + info.setLengthInMS(_ms); + info.setFormat(_fmt); + } + int getByteOrder() const { return byteOrder; } + long getSize() const { return size; } + int getSRate() const { return sRate; } + bool getStereo() const { return stereo; } + bool createPrint() { + const char *print = ofa_create_print(samples, byteOrder, size, sRate, stereo); + if (!print) + return false; + info.setPrint(print); + return true; + } + // Get your unique key at http://www.musicdns.org + TrackInformation *getMetadata(string client_key, string client_version, + bool metadataFlag) + { + if (!retrieve_metadata(client_key, client_version, &info, metadataFlag)) + return 0; + return &info; + } +}; + +#endif
