Skip to content
This repository was archived by the owner on Mar 28, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions codegen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
#
# To make it easy to use with GNU Parallel, e.g.,
# parallel codegen.sh ::: *.mp3
#
echoprint-codegen -h "$1" > "$1.json"
47 changes: 47 additions & 0 deletions src/Codegen.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ using std::string;
using std::vector;

Codegen::Codegen(const float* pcm, unsigned int numSamples, int start_offset) {
for (int i = 0; i < 2; ++i) {
is_code_string_cached[i] = false;
}

if (Params::AudioStreamInput::MaxSamples < (uint)numSamples)
throw std::runtime_error("File was too big\n");

Expand All @@ -38,7 +42,11 @@ Codegen::Codegen(const float* pcm, unsigned int numSamples, int start_offset) {
Fingerprint *pFingerprint = new Fingerprint(pSubbandAnalysis, start_offset);
pFingerprint->Compute();

#if defined(UNHASHED_CODES)
_CodeString = createCodeStringJSON(pFingerprint->getCodes());
#else
_CodeString = createCodeString(pFingerprint->getCodes());
#endif
_NumCodes = pFingerprint->getCodes().size();

delete pFingerprint;
Expand All @@ -63,6 +71,24 @@ string Codegen::createCodeString(vector<FPCode> vCodes) {
return compress(codestream.str());
}

string Codegen::createCodeStringJSON(vector<FPCode> vCodes) {
std::ostringstream codestream;
codestream << "[";
for (uint i = 0; i < vCodes.size(); i++) {
int hash = vCodes[i].code;
// codestream << std::setw(5) << hash;
codestream << "[" << vCodes[i].frame << ", "
<< ((hash >> 20) & 7) << ", "
<< ((hash >> 10) & 1023) << ", "
<< ((hash >> 0) & 1023)
<< "]";
if (i < vCodes.size()-1) {
codestream << ", ";
}
}
codestream << "]";
return codestream.str();
}

string Codegen::compress(const string& s) {
long max_compressed_length = s.size()*2;
Expand All @@ -89,3 +115,24 @@ string Codegen::compress(const string& s) {
delete [] compressed;
return encoded;
}

std::string Codegen::getCodeString(bool human_readable) {
const uint n = human_readable;
if (!is_code_string_cached[n]) {
is_code_string_cached[n] = true;
if (human_readable) {
if (_CodeString.size() > 0) {
code_string_cache[n] = _CodeString;
} else {
code_string_cache[n] = "[]";
}
} else {
if (_CodeString.size() > 0) {
code_string_cache[n] = '"' + compress(_CodeString) + '"';
} else {
code_string_cache[n] = "\"\"";
}
}
}
return code_string_cache[n];
}
6 changes: 5 additions & 1 deletion src/Codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,20 @@ class CODEGEN_API Codegen {
public:
Codegen(const float* pcm, unsigned int numSamples, int start_offset);

std::string getCodeString(){return _CodeString;}
std::string getCodeString(bool human_readable);

int getNumCodes(){return _NumCodes;}
static double getVersion() { return ECHOPRINT_VERSION; }
private:
Fingerprint* computeFingerprint(SubbandAnalysis *pSubbandAnalysis, int start_offset);
std::string createCodeString(std::vector<FPCode> vCodes);
std::string createCodeStringJSON(std::vector<FPCode> vCodes);

std::string compress(const std::string& s);
std::string _CodeString;
int _NumCodes;
bool is_code_string_cached[2];
std::string code_string_cache[2];
};

#endif
28 changes: 24 additions & 4 deletions src/Fingerprint.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "win_funcs.h"
#endif

#define SATURATE(var, val) if ((var) > (val)) (var) = (val);

unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed ) {
// MurmurHash2, by Austin Appleby http://sites.google.com/site/murmurhash/
// m and r are constants set by austin
Expand Down Expand Up @@ -182,13 +184,18 @@ uint Fingerprint::quantized_time_for_frame_absolute(uint frame) {

void Fingerprint::Compute() {
uint actual_codes = 0;
#if !defined(UNHASHED_CODES)
unsigned char hash_material[5];
for(uint i=0;i<5;i++) hash_material[i] = 0;
#endif
uint * onset_counter_for_band;
matrix_u out;
uint onset_count = adaptiveOnsets(345, out, onset_counter_for_band);
_Codes.resize(onset_count*6);

#if defined(UNHASHED_CODES)
assert(SUBBANDS <= 8);
#endif
for(unsigned char band=0;band<SUBBANDS;band++) {
if (onset_counter_for_band[band]>2) {
for(uint onset=0;onset<onset_counter_for_band[band]-2;onset++) {
Expand All @@ -200,7 +207,7 @@ void Fingerprint::Compute() {
p[0][i] = 0;
p[1][i] = 0;
}
int nhashes = 6;
uint nhashes = 6;

if ((int)onset == (int)onset_counter_for_band[band]-4) { nhashes = 3; }
if ((int)onset == (int)onset_counter_for_band[band]-3) { nhashes = 1; }
Expand All @@ -222,16 +229,29 @@ void Fingerprint::Compute() {
}

// For each pair emit a code
for(uint k=0;k<6;k++) {
for(uint k=0; k < nhashes; k++) {
// Quantize the time deltas to 23ms
short time_delta0 = (short)quantized_time_for_frame_delta(p[0][k]);
short time_delta1 = (short)quantized_time_for_frame_delta(p[1][k]);
if (k == 0 && time_delta0 == 0 && time_delta1 == 0) {
continue;
}
uint hashed_code;
#if defined(UNHASHED_CODES)
assert(time_delta0 <= 1023);
assert(time_delta1 <= 1023);
#if defined(NDEBUG)
SATURATE(time_delta0, 1023);
SATURATE(time_delta1, 1023);
#endif
hashed_code = ((band & 7) << 20) | ((time_delta0 & 1023) << 10) | (time_delta1 & 1023);
#else
// Create a key from the time deltas and the band index
memcpy(hash_material+0, (const void*)&time_delta0, 2);
memcpy(hash_material+2, (const void*)&time_delta1, 2);
memcpy(hash_material+4, (const void*)&band, 1);
uint hashed_code = MurmurHash2(&hash_material, 5, HASH_SEED) & HASH_BITMASK;

hashed_code = MurmurHash2(&hash_material, 5, HASH_SEED) & HASH_BITMASK;
#endif
// Set the code alongside the time of onset
_Codes[actual_codes++] = FPCode(time_for_onset_ms_quantized, hashed_code);
//fprintf(stderr, "whee %d,%d: [%d, %d] (%d, %d), %d = %u at %d\n", actual_codes, k, time_delta0, time_delta1, p[0][k], p[1][k], band, hashed_code, time_for_onset_ms_quantized);
Expand Down
7 changes: 4 additions & 3 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ VERSION_COMPAT := $(word 1, $(EP_VERSION)).$(word 2, $(EP_VERSION))
UNAME := $(shell uname -s)
CXX=g++
CC=gcc
#OPTFLAGS=-g -O0
OPTFLAGS=-O3 -DBOOST_UBLAS_NDEBUG -DNDEBUG
#OPTFLAGS=-g -O0 -DUNHASHED_CODES
OPTFLAGS=-O3 -DBOOST_UBLAS_NDEBUG -DNDEBUG -DUNHASHED_CODES
BOOST_CFLAGS=-I/usr/local/include/boost-1_35
TAGLIB_CFLAGS=`taglib-config --cflags`
TAGLIB_LIBS=`taglib-config --libs`
Expand All @@ -26,7 +26,8 @@ MODULES_LIB = \
Fingerprint.o \
MatrixUtility.o \
SubbandAnalysis.o \
Whitening.o
Whitening.o \
functions.o
MODULES = $(MODULES_LIB) Metadata.o

all: libcodegen echoprint-codegen
Expand Down
132 changes: 132 additions & 0 deletions src/functions.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#include <stdio.h>
#include <string.h>
#include <memory>
#ifndef _WIN32
#include <libgen.h>
#include <dirent.h>
#endif
#include <stdlib.h>
#include <stdexcept>

#include "AudioStreamInput.h"
#include "Metadata.h"
#include "Codegen.h"
#include <string>
#include "functions.h"

using namespace std;


// deal with quotes etc in json
std::string escape(const string& value) {
std::string s(value);
std::string out = "";
out.reserve(s.size());
for (size_t i = 0; i < s.size(); i++) {
char c = s[i];
if ((unsigned char)c < 31)
continue;

switch (c) {
case '"' : out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\b': out += "\\b" ; break;
case '\f': out += "\\f" ; break;
case '\n': out += "\\n" ; break;
case '\r': out += "\\r" ; break;
case '\t': out += "\\t" ; break;
// case '/' : out += "\\/" ; break; // Unnecessary?
default:
out += c;
// TODO: do something with unicode?
}
}

return out;
}


codegen_response_t *codegen_file(char* filename, int start_offset, int duration, int tag) {
// Given a filename, perform a codegen on it and get the response
// This is called by a thread
double t1 = now();
codegen_response_t *response = (codegen_response_t *)malloc(sizeof(codegen_response_t));
response->error = NULL;
response->codegen = NULL;

auto_ptr<FfmpegStreamInput> pAudio(new FfmpegStreamInput());
pAudio->ProcessFile(filename, start_offset, duration);

if (pAudio.get() == NULL) { // Unable to decode!
char* output = (char*) malloc(16384);
sprintf(output,"{\"error\":\"could not create decoder\", \"tag\":%d, \"metadata\":{\"filename\":\"%s\"}}",
tag,
escape(filename).c_str());
response->error = output;
return response;
}

int numSamples = pAudio->getNumSamples();

if (numSamples < 1) {
char* output = (char*) malloc(16384);
sprintf(output,"{\"error\":\"could not decode\", \"tag\":%d, \"metadata\":{\"filename\":\"%s\"}}",
tag,
escape(filename).c_str());
response->error = output;
return response;
}
t1 = now() - t1;

double t2 = now();
Codegen *pCodegen = new Codegen(pAudio->getSamples(), numSamples, start_offset);
t2 = now() - t2;

response->t1 = t1;
response->t2 = t2;
response->numSamples = numSamples;
response->codegen = pCodegen;
response->start_offset = start_offset;
response->duration = duration;
response->tag = tag;
response->filename = filename;

return response;
}

char *make_json_string(codegen_response_t* response, bool human_readable_code) {

if (response->error != NULL) {
return response->error;
}

// Get the ID3 tag information.
auto_ptr<Metadata> pMetadata(new Metadata(response->filename));

// preamble + codelen
char* output = (char*) malloc(sizeof(char)*(16384 + strlen(response->codegen->getCodeString(human_readable_code).c_str()) ));

sprintf(output,"{\"metadata\":{\"artist\":\"%s\", \"release\":\"%s\", \"title\":\"%s\", \"genre\":\"%s\", \"bitrate\":%d,"
"\"sample_rate\":%d, \"duration\":%d, \"filename\":\"%s\", \"samples_decoded\":%d, \"given_duration\":%d,"
" \"start_offset\":%d, \"version\":%2.2f, \"codegen_time\":%2.6f, \"decode_time\":%2.6f}, \"code_count\":%d,"
" \"code\":%s, \"tag\":%d}",
escape(pMetadata->Artist()).c_str(),
escape(pMetadata->Album()).c_str(),
escape(pMetadata->Title()).c_str(),
escape(pMetadata->Genre()).c_str(),
pMetadata->Bitrate(),
pMetadata->SampleRate(),
pMetadata->Seconds(),
escape(response->filename).c_str(),
response->numSamples,
response->duration,
response->start_offset,
response->codegen->getVersion(),
response->t2,
response->t1,
response->codegen->getNumCodes(),
response->codegen->getCodeString(human_readable_code).c_str(),
response->tag
);
return output;
}
32 changes: 32 additions & 0 deletions src/functions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <stdio.h>
#include <string.h>
#include <memory>
#ifndef _WIN32
#include <libgen.h>
#include <dirent.h>
#endif
#include <stdlib.h>
#include <stdexcept>

#include "AudioStreamInput.h"
#include "Metadata.h"
#include "Codegen.h"
#include <string>

// The response from the codegen. Contains all the fields necessary
// to create a json string.
typedef struct {
char *error;
char *filename;
int start_offset;
int duration;
int tag;
double t1;
double t2;
int numSamples;
Codegen* codegen;
} codegen_response_t;

codegen_response_t *codegen_file(char*, int, int, int);
std::string escape(const string& value);
char *make_json_string(codegen_response_t* response, bool human_readable_code);
Loading