Skip to content
This repository has been archived by the owner on Mar 28, 2022. It is now read-only.

conditional to skip wasteful (possibly harmful) FPCodes with no IOI data #38

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
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