Add Echoprint library and a new fingerprinting classs that uses it.

This commit is contained in:
John Maguire 2011-06-25 17:30:12 +00:00
parent 2ef9ab6f6e
commit 1df5db5ee3
39 changed files with 2626 additions and 0 deletions

3
3rdparty/echoprint-codegen/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.o
*.dylib
*.so

8
3rdparty/echoprint-codegen/AUTHORS vendored Normal file
View File

@ -0,0 +1,8 @@
echoprint-codegen was written by
Dan Ellis <dpwe@ee.columbia.edu>
Brian Whitman <brian@echonest.com>
Alastair Porter <alastair@porter.net.nz>
and is Copyright 2011 The Echo Nest Corporation.

View File

@ -0,0 +1,18 @@
set(ECHOPRINT-SOURCES
src/AudioBufferInput.cxx
src/AudioStreamInput.cxx
src/Base64.cxx
src/Codegen.cxx
src/Fingerprint.cxx
src/MatrixUtility.cxx
src/Metadata.cxx
src/SubbandAnalysis.cxx
src/Whitening.cxx
)
include_directories(${Boost_INCLUDE_DIRS})
add_library(echoprint STATIC ${ECHOPRINT-SOURCES})
set_target_properties(echoprint PROPERTIES
COMPILE_DEFINITIONS "BOOST_UBLAS_NDEBUG;NDEBUG"
COMPILE_FLAGS "-O3 -fPIC")

44
3rdparty/echoprint-codegen/LICENSE vendored Normal file
View File

@ -0,0 +1,44 @@
echoprint-codegen is open source software licensed under the "MIT License"
More information about the MIT License: http://en.wikipedia.org/wiki/MIT_License
Copyright (c) 2011 The Echo Nest Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
libcodegen makes use of the following pieces of software:
- Murmurhash by Austin Appleby (Public Domain / MIT)
http://sites.google.com/site/murmurhash/
- Boost (Boost Software License)
http://www.boost.org/users/license.html
- Base64.cpp and Base64.h, see source files for license
Copyright (C) 2004-2008 René Nyffenegger
codegen (the example binary that the makefile also builds) makes use of libcodegen and:
- Taglib (LGPL)
http://developer.kde.org/~wheeler/taglib.html
- ffmpeg (via system shell, you must install ffmpeg on your own)
http://www.ffmpeg.org/legal.html

83
3rdparty/echoprint-codegen/README.md vendored Normal file
View File

@ -0,0 +1,83 @@
# Codegen for Echoprint
Echoprint is an open source music fingerprint and resolving framework powered by the [The Echo Nest](http://the.echonest.com/ "The Echo Nest"). The [code generator](http://github.com/echonest/echoprint-codegen "echoprint-codegen") (library to convert PCM samples from a microphone or file into Echoprint codes) is open source (MIT licensed) and free for any use. The [server component](http://github.com/echonest/echoprint-server "echoprint-server") that stores and resolves queries is open source (Apache 2 licensed) and free for any use. The [data for resolving to millions of songs](http://echoprint.me/data "Echoprint Data") is free for any use provided any changes or additions are merged back to the community.
[Read more about Echoprint here](http://echoprint.me)
There are two modes of operation of the Echoprint codegen:
1. the codegen library (libcodegen) is meant to be linked into code that passes it a buffer of PCM data and will output a code string.
2. the codegen binary runs standalone, accepts filenames as inputs and runs in a multithreaded worker mode.
## Requirements for libcodegen
* Boost >= 1.35
## Additional requirements for the codegen binary
* [TagLib](http://developer.kde.org/~wheeler/taglib.html "TagLib")
* ffmpeg - this is called via shell and is not linked into codegen
## Notes about libcodegen:
Code generation takes a buffer of floating point PCM data sampled at 11025 Hz and mono.
Codegen * pCodegen = new Codegen(const float* pcm, uint numSamples, int start_offset);
pcm: a buffer of floats, mono, 11025 Hz
numSamples: the number of samples
start_offset: creates a hint to the server on where the sample is taken from in the original file if known
string code = pCodegen->getCodeString();
The code string is just a base64 encoding of a zlib compression of the original code string, which is a hex encoded series of ASCII numbers. See API/fp.py in echoprint-server for decoding help.
You only need to query for 20 seconds of audio to get a result.
## Notes about the codegen binary
The makefile builds an example code generator that uses libcodegen, called "codegen." This code generator has more features -- it will output ID3 tag information and uses ffmpeg to decode any type of file. If you don't need to compile libcodegen into your app you can rely on this. Note that you need to have ffmpeg installed and accessible on your path for this to work.
./echoprint-codegen billie_jean.mp3 10 30
Will take 30 seconds of audio from 10 seconds into the file and output JSON suitable for querying:
{"metadata":{"artist":"Michael jackson", "release":"800 chansons des annes 80", "title":"Billie jean", "genre":"", "bitrate":192, "sample_rate":44100, "seconds":294, "filename":"billie_jean.mp3", "samples_decoded":220598, "given_duration":30, "start_offset":10, "version":4.00}, "code_count":846, "code":"JxVlIuNwzAMQ1fxCDL133+xo1rnGqNAEcWy/ERa2aKeZmW...
You can POST this JSON directly to the Echo Nest's [song/identify](http://developer.echonest.com/docs/v4/song.html#identify "song/identify") (who has an Echoprint server booted), for example:
curl -F "query=@post_string" http://developer.echonest.com/api/v4/song/identify?api_key=YOUR_KEY
{"fp_lookup_time_ms": 21, "results": [{"songID": "SOAFVGQ1280ED4E371", "match_type": "fp", "title": "Billie Jean", "artist": "Michael Jackson", "artistID": "ARXPPEY1187FB51DF4", "score": 63, "release": "Thriller"}]
(you can also use GET, see the API description)
Or you can host your own [Echoprint server](http://github.com/echonest/echoprint-server "echoprint-server") and ingest or query to that.
Codegen also runs in a multithreaded mode for bulk resolving:
./echoprint-codegen -s 10 30 < file_list
Will compute codes for every file in file_list for 30 seconds starting at 10 seconds. (It tries to be smart about the number of threads to use.) It will output a JSON list. Note that song/identify can accept lists in the JSON, which will be faster than sending each code one at a time. The "tag" parameter is added to each code dictionary to match the resolving material.
## Statistics
### Speed
Codegen scans audio at roughly 250x real time per processor after decoding and resampling to 11025 Hz. This means a full song can be scanned in less than 0.5s on an average computer, and an amount of audio suitable for querying (30s) can be scanned in less than 0.04s.
Decoding from MP3 will be the bottleneck for most implementations. Decoders like mpg123 or ffmpeg can decode 30s mp3 audio to 11025 PCM in under 0.10s.
clump:echoprint-codegen bwhitman$ time mpg123 -q -s -4 -n 1200 song.mp3 > /dev/null
real 0m0.079s
user 0m0.067s
sys 0m0.007s
### Accuracy
Look at http://echoprint.me for information on the accuracy of the echoprint system.
## FAQ
Q: I get "Couldn't decode any samples with: ffmpeg" when running codegen
A: When running the example code generator (echoprint-codegen) make sure ffmpeg is accessible to your path. Try running ffmpeg filename.mp3 on the file you are testing the code generator with. If it doesn't work, codegen won't work.

View File

@ -0,0 +1,28 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include <string.h>
#include <limits.h>
#include <assert.h>
#include "AudioBufferInput.h"
AudioBufferInput::AudioBufferInput() { }
void AudioBufferInput::SetBuffer(const float* pBuffer, uint numSamples) {
_NumberSamples = numSamples;
_pSamples = new float[_NumberSamples]; // base-class destructor will clean this up.
memcpy(_pSamples, pBuffer, numSamples*sizeof(float));
}
void AudioBufferInput::SaveBuffer(const char*filename) {
FILE *out = fopen(filename,"wb");
fwrite(&_NumberSamples, sizeof(int), 1, out);
uint mn = 1;
fwrite(&mn, sizeof(int), 1, out);
fwrite(_pSamples, 4, _NumberSamples, out);
fclose(out);
}

View File

@ -0,0 +1,30 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include "Common.h"
#include <iostream>
#include <string>
#ifndef AUDIOBUFFERINPUT_H
#define AUDIOBUFFERINPUT_H
#include "Params.h"
#include "AudioStreamInput.h"
class AudioBufferInput : public AudioStreamInput {
public:
AudioBufferInput();
std::string GetName() {return "direct buffer";}
void SaveBuffer(const char*filename);
void SetBuffer(const float* pBuffer, uint numSamples);
protected:
std::string GetCommandLine(const char*){return "";}
private:
};
#endif

View File

@ -0,0 +1,132 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include <stddef.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#ifndef _WIN32
#include <unistd.h>
#define POPEN_MODE "r"
#else
#include "win_unistd.h"
#include <winsock.h>
#define POPEN_MODE "rb"
#endif
#include <string.h>
#include "AudioStreamInput.h"
#include "Common.h"
#include "Params.h"
using std::string;
namespace FFMPEG {
// Do we think FFmpeg will read this as an audio file?
bool IsAudioFile(const char* pFileName) {
static const char* supportedExtensions[] = {".mp3", ".m4a", ".mp4", ".aif", ".aiff", ".flac", ".au", ".wav", ".aac", ".flv"};
// Not an exhaustive list. ogg and rm could be added if tested.
for (uint i = 0; i < NELEM(supportedExtensions); i++) {
if (File::ends_with(pFileName, supportedExtensions[i]))
return true;
}
return false;
}
}
bool AudioStreamInput::IsSupported(const char *path) {
return true; // Take a crack at anything, by default. The worst thing that will happen is that we fail.
}
AudioStreamInput::AudioStreamInput() : _pSamples(NULL), _NumberSamples(0), _Offset_s(0), _Seconds(0) {}
AudioStreamInput::~AudioStreamInput() {
if (_pSamples != NULL)
delete [] _pSamples, _pSamples = NULL;
}
bool AudioStreamInput::ProcessFile(const char* filename, int offset_s/*=0*/, int seconds/*=0*/) {
if (!File::Exists(filename) || !IsSupported(filename))
return false;
_Offset_s = offset_s;
_Seconds = seconds;
std::string message = GetCommandLine(filename);
FILE* fp = popen(message.c_str(), POPEN_MODE);
bool ok = (fp != NULL);
if (ok)
{
bool did_work = ProcessFilePointer(fp);
bool succeeded = !pclose(fp);
ok = did_work && succeeded;
}
else
fprintf(stderr, "AudioStreamInput::ProcessFile can't open %s\n", filename);
return ok;
}
// reads raw signed 16-bit shorts from a file
bool AudioStreamInput::ProcessRawFile(const char* rawFilename) {
FILE* fp = fopen(rawFilename, "r"); // TODO: Windows
bool ok = (fp != NULL);
if (ok)
{
ok = ProcessFilePointer(fp);
fclose(fp);
}
return ok;
}
// reads raw signed 16-bit shorts from stdin, for example:
// ffmpeg -i fille.mp3 -f s16le -ac 1 -ar 11025 - | TestAudioSTreamInput
bool AudioStreamInput::ProcessStandardInput(void) {
// TODO - Windows will explodey at not setting O_BINARY on stdin.
return ProcessFilePointer(stdin);
}
bool AudioStreamInput::ProcessFilePointer(FILE* pFile) {
std::vector<short*> vChunks;
uint nSamplesPerChunk = (uint) Params::AudioStreamInput::SamplingRate * Params::AudioStreamInput::SecondsPerChunk;
uint samplesRead = 0;
do {
short* pChunk = new short[nSamplesPerChunk];
samplesRead = fread(pChunk, sizeof (short), nSamplesPerChunk, pFile);
_NumberSamples += samplesRead;
vChunks.push_back(pChunk);
} while (samplesRead > 0);
// Convert from shorts to 16-bit floats and copy into sample buffer.
uint sampleCounter = 0;
_pSamples = new float[_NumberSamples];
uint samplesLeft = _NumberSamples;
for (uint i = 0; i < vChunks.size(); i++)
{
short* pChunk = vChunks[i];
uint numSamples = samplesLeft < nSamplesPerChunk ? samplesLeft : nSamplesPerChunk;
for (uint j = 0; j < numSamples; j++)
_pSamples[sampleCounter++] = (float) pChunk[j] / 32768.0f;
samplesLeft -= numSamples;
delete [] pChunk, vChunks[i] = NULL;
}
assert(samplesLeft == 0);
int error = ferror(pFile);
bool success = error == 0;
if (!success)
perror("ProcessFilePointer error");
return success;
}

View File

@ -0,0 +1,98 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef AUDIOSTREAMINPUT_H
#define AUDIOSTREAMINPUT_H
#include "Common.h"
#include "Params.h"
#include <iostream>
#include <string>
#include <math.h>
#include "File.h"
#ifdef _WIN32
#define and &&
#define snprintf _snprintf
#endif
class AudioStreamInput {
public:
AudioStreamInput();
virtual ~AudioStreamInput();
virtual bool ProcessFile(const char* filename, int offset_s=0, int seconds=0);
virtual std::string GetName() = 0;
bool ProcessRawFile(const char* rawFilename);
bool ProcessStandardInput(void);
bool ProcessFilePointer(FILE* pFile);
int getNumSamples() const {return _NumberSamples;}
const float* getSamples() {return _pSamples;}
double getDuration() { return (double)getNumSamples() / Params::AudioStreamInput::SamplingRate; }
virtual bool IsSupported(const char* pFileName); //Everything ffmpeg can do, by default
int GetOffset() const { return _Offset_s;}
int GetSeconds() const { return _Seconds;}
protected:
virtual std::string GetCommandLine(const char* filename) = 0;
static bool ends_with(const char *s, const char *ends_with);
float* _pSamples;
uint _NumberSamples;
int _Offset_s;
int _Seconds;
};
class StdinStreamInput : public AudioStreamInput {
public:
std::string GetName(){return "stdin";};
protected:
bool IsSupported(const char* pFileName){ return (std::string("stdin") == pFileName);};
bool ProcessFile(const char* filename, int offset_s=0, int seconds=0){ return ProcessStandardInput();}
virtual std::string GetCommandLine(const char* filename){return "";} // hack
};
class FfmpegStreamInput : public AudioStreamInput {
public:
std::string GetName(){return "ffmpeg";};
protected:
std::string GetCommandLine(const char* filename) {
// TODO: Windows
char message[4096] = {0};
if (_Offset_s == 0 and _Seconds == 0)
snprintf(message, NELEM(message), "ffmpeg -i \"%s\" -ac %d -ar %d -f s16le - 2>/dev/null",
filename, Params::AudioStreamInput::Channels, (uint) Params::AudioStreamInput::SamplingRate);
else
snprintf(message, NELEM(message), "ffmpeg -i \"%s\" -ac %d -ar %d -f s16le -t %d -ss %d - 2>/dev/null",
filename, Params::AudioStreamInput::Channels, (uint) Params::AudioStreamInput::SamplingRate, _Seconds, _Offset_s);
printf("%s\n", message);
return std::string(message);
}
};
namespace FFMPEG {
bool IsAudioFile(const char* pFileName);
};
class Mpg123StreamInput : public AudioStreamInput {
public:
std::string GetName(){return "mpg123";};
protected:
#define FRAMES_PER_SECOND 38.2813f
bool IsSupported(const char* pFileName){ return File::ends_with(pFileName, ".mp3");};
std::string GetCommandLine(const char* filename) {
char message[4096] = {0};
if (_Offset_s == 0 and _Seconds == 0)
snprintf(message, NELEM(message), "mpg123 --quiet --singlemix --stdout --rate %d \"%s\"",
(uint) Params::AudioStreamInput::SamplingRate, filename);
else
snprintf(message, NELEM(message), "mpg123 --quiet --singlemix --stdout --rate %d --skip %d --frames %d \"%s\"",
(uint) Params::AudioStreamInput::SamplingRate, (uint)(_Offset_s * FRAMES_PER_SECOND) /* unprecise */, (uint)ceilf(_Seconds * FRAMES_PER_SECOND) /* unprecise */, filename);
return std::string(message);
}
};
#endif

View File

@ -0,0 +1,140 @@
/*
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
*/
#include "Base64.h"
#include <iostream>
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static const std::string base64_chars_url =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-_";
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
static inline bool is_base64_url(unsigned char c) {
return (isalnum(c) || (c == '-') || (c == '_'));
}
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len, bool url) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++) {
if (url)
ret += base64_chars_url[char_array_4[i]];
else
ret += base64_chars[char_array_4[i]];
}
i = 0;
}
}
if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++){
if (url)
ret += base64_chars_url[char_array_4[j]];
else
ret += base64_chars[char_array_4[j]];
}
while((i++ < 3))
ret += '=';
}
return ret;
}
std::string base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;
for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}

View File

@ -0,0 +1,9 @@
#ifndef BASE64_H
#define BASE64_H
#include <string>
std::string base64_encode(unsigned char const* , unsigned int len, bool url);
std::string base64_decode(std::string const& s);
#endif

View File

@ -0,0 +1,84 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include <sstream>
#include <iostream>
#include <iomanip>
#include "Codegen.h"
#include "AudioBufferInput.h"
#include "Fingerprint.h"
#include "Whitening.h"
#include "Base64.h"
#include <zlib.h>
Codegen::Codegen(const float* pcm, uint numSamples, int start_offset) {
if (Params::AudioStreamInput::MaxSamples < (uint)numSamples)
throw std::runtime_error("File was too big\n");
Whitening *pWhitening = new Whitening(pcm, numSamples);
pWhitening->Compute();
AudioBufferInput *pAudio = new AudioBufferInput();
pAudio->SetBuffer(pWhitening->getWhitenedSamples(), pWhitening->getNumSamples());
SubbandAnalysis *pSubbandAnalysis = new SubbandAnalysis(pAudio);
pSubbandAnalysis->Compute();
Fingerprint *pFingerprint = new Fingerprint(pSubbandAnalysis, start_offset);
pFingerprint->Compute();
_CodeString = createCodeString(pFingerprint->getCodes());
_NumCodes = pFingerprint->getCodes().size();
delete pFingerprint;
delete pSubbandAnalysis;
delete pWhitening;
delete pAudio;
}
string Codegen::createCodeString(vector<FPCode> vCodes) {
if (vCodes.size() < 3) {
return "";
}
std::ostringstream codestream;
codestream << std::setfill('0') << std::hex;
for (uint i = 0; i < vCodes.size(); i++)
codestream << std::setw(5) << vCodes[i].frame;
for (uint i = 0; i < vCodes.size(); i++) {
int hash = vCodes[i].code;
codestream << std::setw(5) << hash;
}
return compress(codestream.str());
}
string Codegen::compress(const string& s) {
long max_compressed_length = s.size()*2;
unsigned char *compressed = new unsigned char[max_compressed_length];
// zlib the code string
z_stream stream;
stream.next_in = (Bytef*)(unsigned char*)s.c_str();
stream.avail_in = (uInt)s.size();
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = (voidpf)0;
deflateInit(&stream, Z_DEFAULT_COMPRESSION);
do {
stream.next_out = compressed;
stream.avail_out = max_compressed_length;
if(deflate(&stream, Z_FINISH) == Z_STREAM_END) break;
} while (stream.avail_out == 0);
uint compressed_length = stream.total_out;
deflateEnd(&stream);
// base64 the zlib'd code string
string encoded = base64_encode(compressed, compressed_length, true);
delete [] compressed;
return encoded;
}

View File

@ -0,0 +1,53 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef CODEGEN_H
#define CODEGEN_H
// Entry point for generating codes from PCM data.
#define VERSION 4.11
#include <memory>
#include <string>
#include "Common.h"
#include "AudioBufferInput.h"
#include "SubbandAnalysis.h"
#include "Fingerprint.h"
using namespace std;
#ifdef _MSC_VER
#ifdef CODEGEN_EXPORTS
#define CODEGEN_API __declspec(dllexport)
#pragma message("Exporting codegen.dll")
#else
#define CODEGEN_API __declspec(dllimport)
#pragma message("Importing codegen.dll")
#endif
#else
#define CODEGEN_API
#endif
class Fingerprint;
class CODEGEN_API Codegen {
public:
Codegen(const float* pcm, uint numSamples, int start_offset);
string getCodeString(){return _CodeString;}
int getNumCodes(){return _NumCodes;}
float getVersion() { return VERSION; }
private:
Fingerprint* computeFingerprint(SubbandAnalysis *pSubbandAnalysis, int start_offset);
string createCodeString(vector<FPCode> vCodes);
string compress(const string& s);
string _CodeString;
int _NumCodes;
};
#endif

55
3rdparty/echoprint-codegen/src/Common.h vendored Normal file
View File

@ -0,0 +1,55 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef COMMON_H
#define COMMON_H
#include <assert.h>
#ifndef _WIN32
#include <sys/time.h>
#else
#include "win_funcs.h"
#include <sys/types.h>
/* for STL*/
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <malloc.h>
#endif
#include <float.h>
#include <stdio.h>
#include <stdarg.h>
#ifndef NULL
#define NULL 0
#endif
// Returns the current date in seconds. The precision is in microseconds.
static inline double now (void) {
struct timeval tv;
double now;
gettimeofday (&tv, NULL);
now = 1e-6 * tv.tv_usec + tv.tv_sec;
return now;
}
typedef unsigned int uint;
#define NELEM(array) (sizeof(array) / sizeof(array[0]))
#ifndef _WIN32
#define EN_ARRAY(type,var,size) type var[size]
#else
#define EN_ARRAY(type,var,size) type* var = (type*) _alloca((size)*sizeof(type))
#endif
#endif

50
3rdparty/echoprint-codegen/src/File.h vendored Normal file
View File

@ -0,0 +1,50 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef FILE_H
#define FILE_H
#include <string.h>
#ifdef _WIN32
#include "win_unistd.h"
#endif
/*
This makes file writing a bit easier (makes sure we don't forget to fclose, basically). Use it like this:
bool WriteStuffToFile(const char* filename)
{
File f(filename);
if (f)
fprintf(f, "stuff I want to print: %s", stuff);
return f; // success/failure
}
*/
class File {
public:
File(const char* filename){_f = fopen(filename, "w");};
~File(){fclose(_f); _f = NULL;}
operator bool(){return _f != NULL;}
operator FILE*(){return _f;}
static bool Exists(const char* filename){return (access(filename, F_OK) == 0);}
static bool ends_with(const char* filename, const char* ending) {
int nFilename = strlen(filename);
int nEnding = strlen(ending);
bool same = false;
if (nEnding <= nFilename) {
const char* file_end = filename + strlen(filename) - strlen(ending);
for (int i = 0; i < nEnding; i++)
if (tolower(file_end[i]) != tolower(ending[i])) return false;
same = true;
}
return same;
}
private:
FILE* _f;
};
#endif

View File

@ -0,0 +1,238 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include "Fingerprint.h"
#include "Params.h"
#include <string.h>
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
const unsigned int m = 0x5bd1e995;
const int r = 24;
// Initialize the hash to a 'random' value
unsigned int h = seed ^ len;
// Mix 4 bytes at a time into the hash
const unsigned char * data = (const unsigned char *)key;
while(len >= 4) {
unsigned int k = *(unsigned int *)data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
// Handle the last few bytes of the input array
switch(len) {
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0];
h *= m;
};
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
Fingerprint::Fingerprint(SubbandAnalysis* pSubbandAnalysis, int offset)
: _pSubbandAnalysis(pSubbandAnalysis), _Offset(offset) { }
uint Fingerprint::adaptiveOnsets(int ttarg, matrix_u&out, uint*&onset_counter_for_band) {
// E is a sgram-like matrix of energies.
const float *pE;
int bands, frames, i, j, k;
int deadtime = 128;
double H[SUBBANDS],taus[SUBBANDS], N[SUBBANDS];
int contact[SUBBANDS], lcontact[SUBBANDS], tsince[SUBBANDS];
double overfact = 1.1; /* threshold rel. to actual peak */
uint onset_counter = 0;
matrix_f E = _pSubbandAnalysis->getMatrix();
// Take successive stretches of 8 subband samples and sum their energy under a hann window, then hop by 4 samples (50% window overlap).
int hop = 4;
int nsm = 8;
float ham[nsm];
for(int i = 0 ; i != nsm ; i++)
ham[i] = .5 - .5*cos( (2.*M_PI/(nsm-1))*i);
int nc = floor((float)E.size2()/(float)hop)-(floor((float)nsm/(float)hop)-1);
matrix_f Eb = matrix_f(nc, 8);
MatrixUtility::clear(Eb);
for(i=0;i<nc;i++) {
for(j=0;j<SUBBANDS;j++) {
for(k=0;k<nsm;k++) Eb(i,j) = Eb(i,j) + ( E(j,(i*hop)+k) * ham[k]);
Eb(i,j) = sqrtf(Eb(i,j));
}
}
frames = Eb.size1();
bands = Eb.size2();
pE = &Eb.data()[0];
out = matrix_u(SUBBANDS, frames);
onset_counter_for_band = new uint[SUBBANDS];
double bn[] = {0.1883, 0.4230, 0.3392}; /* preemph filter */ // new
int nbn = 3;
double a1 = 0.98;
double Y0[SUBBANDS];
for (j = 0; j < bands; ++j) {
onset_counter_for_band[j] = 0;
N[j] = 0.0;
taus[j] = 1.0;
H[j] = pE[j];
contact[j] = 0;
lcontact[j] = 0;
tsince[j] = 0;
Y0[j] = 0;
}
for (i = 0; i < frames; ++i) {
for (j = 0; j < SUBBANDS; ++j) {
double xn = 0;
/* calculate the filter - FIR part */
if (i >= 2*nbn) {
for (int k = 0; k < nbn; ++k) {
xn += bn[k]*(pE[j-SUBBANDS*k] - pE[j-SUBBANDS*(2*nbn-k)]);
}
}
/* IIR part */
xn = xn + a1*Y0[j];
/* remember the last filtered level */
Y0[j] = xn;
contact[j] = (xn > H[j])? 1 : 0;
if (contact[j] == 1 && lcontact[j] == 0) {
/* attach - record the threshold level unless we have one */
if(N[j] == 0) {
N[j] = H[j];
}
}
if (contact[j] == 1) {
/* update with new threshold */
H[j] = xn * overfact;
} else {
/* apply decays */
H[j] = H[j] * exp(-1.0/(double)taus[j]);
}
if (contact[j] == 0 && lcontact[j] == 1) {
/* detach */
if (onset_counter_for_band[j] > 0 && (int)out(j, onset_counter_for_band[j]-1) > i - deadtime) {
// overwrite last-written time
--onset_counter_for_band[j];
--onset_counter;
}
out(j, onset_counter_for_band[j]++) = i;
++onset_counter;
tsince[j] = 0;
}
++tsince[j];
if (tsince[j] > ttarg) {
taus[j] = taus[j] - 1;
if (taus[j] < 1) taus[j] = 1;
} else {
taus[j] = taus[j] + 1;
}
if ( (contact[j] == 0) && (tsince[j] > deadtime)) {
/* forget the threshold where we recently hit */
N[j] = 0;
}
lcontact[j] = contact[j];
}
pE += bands;
}
return onset_counter;
}
// dan is going to beat me if i call this "decimated_time_for_frame" like i want to
uint Fingerprint::quantized_time_for_frame_delta(uint frame_delta) {
double time_for_frame_delta = (double)frame_delta / ((double)Params::AudioStreamInput::SamplingRate / 32.0);
return ((int)floor((time_for_frame_delta * 1000.0) / (float)QUANTIZE_DT_S) * QUANTIZE_DT_S) / floor(QUANTIZE_DT_S*1000.0);
}
uint Fingerprint::quantized_time_for_frame_absolute(uint frame) {
double time_for_frame = _Offset + (double)frame / ((double)Params::AudioStreamInput::SamplingRate / 32.0);
return ((int)rint((time_for_frame * 1000.0) / (float)QUANTIZE_A_S) * QUANTIZE_A_S) / floor(QUANTIZE_A_S*1000.0);
}
void Fingerprint::Compute() {
uint actual_codes = 0;
unsigned char hash_material[5];
for(uint i=0;i<5;i++) hash_material[i] = 0;
uint * onset_counter_for_band;
matrix_u out;
uint onset_count = adaptiveOnsets(345, out, onset_counter_for_band);
_Codes.resize(onset_count*6);
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++) {
// What time was this onset at?
uint time_for_onset_ms_quantized = quantized_time_for_frame_absolute(out(band,onset));
uint p[2][6];
int 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; }
p[0][0] = (out(band,onset+1) - out(band,onset));
p[1][0] = (out(band,onset+2) - out(band,onset+1));
if(nhashes > 1) {
p[0][1] = (out(band,onset+1) - out(band,onset));
p[1][1] = (out(band,onset+3) - out(band,onset+1));
p[0][2] = (out(band,onset+2) - out(band,onset));
p[1][2] = (out(band,onset+3) - out(band,onset+2));
if(nhashes > 3) {
p[0][3] = (out(band,onset+1) - out(band,onset));
p[1][3] = (out(band,onset+4) - out(band,onset+1));
p[0][4] = (out(band,onset+2) - out(band,onset));
p[1][4] = (out(band,onset+4) - out(band,onset+2));
p[0][5] = (out(band,onset+3) - out(band,onset));
p[1][5] = (out(band,onset+4) - out(band,onset+3));
}
}
// For each pair emit a code
for(uint k=0;k<6;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]);
// 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;
// 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);
}
}
}
}
_Codes.resize(actual_codes);
delete [] onset_counter_for_band;
}

View File

@ -0,0 +1,45 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef FINGERPRINT_H
#define FINGERPRINT_H
#include "Common.h"
#include "SubbandAnalysis.h"
#include "MatrixUtility.h"
#include <vector>
#define HASH_SEED 0x9ea5fa36
#define QUANTIZE_DT_S (256.0/11025.0)
#define QUANTIZE_A_S (256.0/11025.0)
#define HASH_BITMASK 0x000fffff
#define SUBBANDS 8
struct FPCode {
FPCode() : frame(0), code(0) {}
FPCode(uint f, int c) : frame(f), code(c) {}
uint frame;
uint code;
};
unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed );
class Fingerprint {
public:
uint quantized_time_for_frame_delta(uint frame_delta);
uint quantized_time_for_frame_absolute(uint frame);
Fingerprint(SubbandAnalysis* pSubbandAnalysis, int offset);
void Compute();
uint adaptiveOnsets(int ttarg, matrix_u&out, uint*&onset_counter_for_band) ;
std::vector<FPCode>& getCodes(){return _Codes;}
protected:
SubbandAnalysis *_pSubbandAnalysis;
int _Offset;
std::vector<FPCode> _Codes;
};
#endif

48
3rdparty/echoprint-codegen/src/Makefile vendored Normal file
View File

@ -0,0 +1,48 @@
UNAME := $(shell uname -s)
CXX=g++
CC=gcc
ARCH=`uname -m`
#OPTFLAGS=-g -O0
OPTFLAGS=-O3 -DBOOST_UBLAS_NDEBUG -DNDEBUG
CXXFLAGS=-Wall -I/usr/local/include/boost-1_35 `taglib-config --cflags` -fPIC $(OPTFLAGS)
CFLAGS=-Wall -fPIC $(OPTFLAGS)
LDFLAGS=`taglib-config --libs` -lz -lpthread $(OPTFLAGS)
MODULES_LIB = \
AudioBufferInput.o \
AudioStreamInput.o \
Base64.o \
Codegen.o \
Fingerprint.o \
MatrixUtility.o \
SubbandAnalysis.o \
Whitening.o
MODULES = $(MODULES_LIB) Metadata.o
main: $(MODULES) main.o
$(CXX) $(MODULES) $(LDFLAGS) main.o -o ../echoprint-codegen
$(CXX) -shared -fPIC -o libcodegen.so $(MODULES_LIB) -lz
ifeq ($(UNAME),Darwin)
libtool -dynamic -flat_namespace -install_name libcodegen.4.1.1.dylib -lSystem -compatibility_version 4.1 -macosx_version_min 10.6 \
-current_version 4.1.1 -o libcodegen.4.1.1.dylib -undefined suppress \
$(MODULES) -framework vecLib -framework Accelerate
endif
%.o: %.c %.h
$(CC) $(CFLAGS) -c -o $@ $<
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
%.o: %.cxx %.h
$(CXX) $(CXXFLAGS) -c -o $@ $<
%.o: %.cxx
$(CXX) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f *.o ../echoprint-codegen
rm -f *.so
ifeq ($(UNAME),Darwin)
rm -f *.dylib
endif

View File

@ -0,0 +1,54 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include "MatrixUtility.h"
// http://www.boost.org/doc/libs/1_35_0/libs/numeric/ublas/doc/matrix.htm
namespace MatrixUtility {
bool TextFileOutput(const matrix_f& A, const char* filename) {
FILE *matrix_file = fopen(filename, "w");
bool success = (matrix_file != NULL);
if (success) {
const float *d = &A.data()[0];
for (uint i = 0; i < A.size1(); i++) {
for (uint j = 0; j < A.size2(); j++)
fprintf(matrix_file, "%2.3f ", d[i*A.size2() + j]);
fprintf(matrix_file, "\n");
}
fclose(matrix_file);
}
return success;
}
bool FileOutput(const matrix_f& A, const char* filename) {
FILE *matrix_file = fopen(filename, "wb");
bool success = (matrix_file != NULL);
if (success) {
uint mm = A.size1();
uint mn = A.size2();
fwrite(&mm, sizeof(int), 1, matrix_file);
fwrite(&mn, sizeof(int), 1, matrix_file);
for (uint i = 0; i< A.size1(); i++) {
for(uint j=0;j<A.size2(); j++) {
const float d = A(i, j);
fwrite(&d, sizeof(float), 1, matrix_file);
}
}
fclose(matrix_file);
}
return success;
}
void clear(matrix_f A) {
for(uint i=0;i<A.size1();i++) for(uint j=0;j<A.size2();j++) A(i,j) = 0.0;
}
} // namespace

View File

@ -0,0 +1,32 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef MATRIXUTILITY_H
#define MATRIXUTILITY_H
#include "Common.h"
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
namespace ublas = boost::numeric::ublas;
typedef ublas::matrix<float> matrix_f;
typedef ublas::matrix<uint> matrix_u;
typedef ublas::matrix_row<matrix_f> matrix_row_f;
typedef ublas::matrix_row<const ublas::matrix<float> > const_matrix_row_f;
typedef ublas::matrix_column<matrix_f> matrix_column_f;
typedef ublas::matrix_range<matrix_f> matrix_range_f;
namespace MatrixUtility {
inline uint rows(matrix_f A){ return A.size1();}
inline uint cols(matrix_f A){ return A.size2();}
bool FileOutput(const matrix_f& A, const char* filename);
bool TextFileOutput(const matrix_f& A, const char* filename);
void clear(matrix_f A);
}
#endif

View File

@ -0,0 +1,33 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include "Metadata.h"
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <iostream>
Metadata::Metadata(const string& file) : _Filename(file), _Artist(""), _Album(""), _Title(""), _Genre(""), _Bitrate(0), _SampleRate(0), _Seconds(0) {
if (file != "stdin") {
// TODO: Consider removing the path from the filename -- not sure if we can do this in a platform-independent way.
TagLib::FileRef f(_Filename.c_str());
TagLib::Tag* tag = f.isNull() ? NULL : f.tag();
if (tag != NULL) {
_Artist = tag->artist().toCString();
_Album = tag->album().toCString();
_Title = tag->title().toCString();
_Genre = tag->genre().toCString();
}
TagLib::AudioProperties* properties = f.isNull() ? NULL : f.audioProperties();
if (properties != NULL) {
_Bitrate = properties->bitrate();
_SampleRate = properties->sampleRate();
_Seconds = properties->length();
}
}
}

View File

@ -0,0 +1,37 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef METADATA_H
#define METADATA_H
#include <string>
using std::string;
class Metadata {
public:
Metadata(const std::string& file);
string Filename() {return _Filename;}
string Artist(){ return _Artist;}
string Album() { return _Album;}
string Title() { return _Title;}
string Genre() { return _Genre;}
int Bitrate() { return _Bitrate;}
int SampleRate(){ return _SampleRate;}
int Seconds() { return _Seconds;}
private:
string _Filename;
string _Artist;
string _Album;
string _Title;
string _Genre;
int _Bitrate;
int _SampleRate;
int _Seconds;
};
#endif

23
3rdparty/echoprint-codegen/src/Params.h vendored Normal file
View File

@ -0,0 +1,23 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef PARAMS_H
#define PARAMS_H
#include "Common.h"
namespace Params {
namespace AudioStreamInput {
const float SamplingRate = 11025.0f;
const uint Channels = 1;
const float SecondsPerChunk = 10.0f;
const uint BytesPerSample = 4; // floats
const uint MaxSamples = 66977792; // TODO: Remove this, or set it intelligently, at least. Good for 32-bit analyzer.
}
}
#endif

View File

@ -0,0 +1,70 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include "SubbandAnalysis.h"
#include "AudioStreamInput.h"
SubbandAnalysis::SubbandAnalysis(AudioStreamInput* pAudio) {
_pSamples = pAudio->getSamples();
_NumSamples = pAudio->getNumSamples();
Init();
}
SubbandAnalysis::SubbandAnalysis(const float* pSamples, uint numSamples) :
_pSamples(pSamples), _NumSamples(numSamples) {
Init();
}
SubbandAnalysis::~SubbandAnalysis() {
}
void SubbandAnalysis::Init() {
// Calculate the analysis filter bank coefficients
_Mr = matrix_f(M_ROWS, M_COLS);
_Mi = matrix_f(M_ROWS, M_COLS);
for (uint i = 0; i < M_ROWS; ++i) {
for (uint k = 0; k < M_COLS; ++k) {
_Mr(i,k) = cos((2*i + 1)*(k-4)*(M_PI/16.0));
_Mi(i,k) = sin((2*i + 1)*(k-4)*(M_PI/16.0));
}
}
}
void SubbandAnalysis::Compute() {
uint t, i, j;
float Z[C_LEN];
float Y[M_COLS];
_NumFrames = (_NumSamples - C_LEN + 1)/SUBBANDS;
assert(_NumFrames > 0);
_Data = matrix_f(SUBBANDS, _NumFrames);
for (t = 0; t < _NumFrames; ++t) {
for (i = 0; i < C_LEN; ++i) {
Z[i] = _pSamples[ t*SUBBANDS + i] * SubbandFilterBank::C[i];
}
for (i = 0; i < M_COLS; ++i) {
Y[i] = Z[i];
}
for (i = 0; i < M_COLS; ++i) {
for (j = 1; j < M_ROWS; ++j) {
Y[i] += Z[i + M_COLS*j];
}
}
for (i = 0; i < M_ROWS; ++i) {
float Dr = 0, Di = 0;
for (j = 0; j < M_COLS; ++j) {
Dr += _Mr(i,j) * Y[j];
Di -= _Mi(i,j) * Y[j];
}
_Data(i,t) = Dr*Dr + Di*Di;
}
}
}

View File

@ -0,0 +1,65 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef SUBBANDANALYSIS_H
#define SUBBANDANALYSIS_H
#include "Common.h"
#include "Params.h"
#include "MatrixUtility.h"
#define C_LEN 128
#define SUBBANDS 8
#define M_ROWS 8
#define M_COLS 16
namespace SubbandFilterBank {
// 128pt, 1/8th band low-pass prototype subsampled from Table_analysis_window
static const float C[C_LEN] = {
0.000000477, 0.000000954, 0.000001431, 0.000002384, 0.000003815, 0.000006199, 0.000009060, 0.000013828,
0.000019550, 0.000027657, 0.000037670, 0.000049591, 0.000062943, 0.000076771, 0.000090599, 0.000101566,
-0.000108242, -0.000106812, -0.000095367, -0.000069618, -0.000027180, 0.000034332, 0.000116348, 0.000218868,
0.000339031, 0.000472546, 0.000611782, 0.000747204, 0.000866413, 0.000954151, 0.000994205, 0.000971317,
-0.000868797, -0.000674248, -0.000378609, 0.000021458, 0.000522137, 0.001111031, 0.001766682, 0.002457142,
0.003141880, 0.003771782, 0.004290581, 0.004638195, 0.004752159, 0.004573822, 0.004049301, 0.003134727,
-0.001800537, -0.000033379, 0.002161503, 0.004756451, 0.007703304, 0.010933399, 0.014358521, 0.017876148,
0.021372318, 0.024725437, 0.027815342, 0.030526638, 0.032754898, 0.034412861, 0.035435200, 0.035780907,
-0.035435200, -0.034412861, -0.032754898, -0.030526638, -0.027815342, -0.024725437, -0.021372318, -0.017876148,
-0.014358521, -0.010933399, -0.007703304, -0.004756451, -0.002161503, 0.000033379, 0.001800537, 0.003134727,
-0.004049301, -0.004573822, -0.004752159, -0.004638195, -0.004290581, -0.003771782, -0.003141880, -0.002457142,
-0.001766682, -0.001111031, -0.000522137, -0.000021458, 0.000378609, 0.000674248, 0.000868797, 0.000971317,
-0.000994205, -0.000954151, -0.000866413, -0.000747204, -0.000611782, -0.000472546, -0.000339031, -0.000218868,
-0.000116348, -0.000034332, 0.000027180, 0.000069618, 0.000095367, 0.000106812, 0.000108242, 0.000101566,
-0.000090599, -0.000076771, -0.000062943, -0.000049591, -0.000037670, -0.000027657, -0.000019550, -0.000013828,
-0.000009060, -0.000006199, -0.000003815, -0.000002384, -0.000001431, -0.000000954, -0.000000477, 0};
}
class AudioStreamInput;
class SubbandAnalysis {
public:
inline SubbandAnalysis() {};
SubbandAnalysis(AudioStreamInput* pAudio);
SubbandAnalysis(const float* pSamples, uint numSamples);
virtual ~SubbandAnalysis();
void Compute();
public:
inline uint getNumFrames() const {return _NumFrames;}
inline uint getNumBands() const {return SUBBANDS;}
const matrix_f& getMatrix() {return _Data;}
protected:
const float* _pSamples;
uint _NumSamples;
uint _NumFrames;
matrix_f _Mi;
matrix_f _Mr;
matrix_f _Data;
private:
void Init();
};
#endif

View File

@ -0,0 +1,109 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#include "Whitening.h"
#include "AudioStreamInput.h"
Whitening::Whitening(AudioStreamInput* pAudio) {
_pSamples = pAudio->getSamples();
_NumSamples = pAudio->getNumSamples();
Init();
}
Whitening::Whitening(const float* pSamples, uint numSamples) :
_pSamples(pSamples), _NumSamples(numSamples) {
Init();
}
Whitening::~Whitening() {
free(_R);
free(_Xo);
free(_ai);
free(_whitened);
}
void Whitening::Init() {
int i;
_P = 40;
_R = (float *)malloc((_P+1)*sizeof(float));
for (i = 0; i <= _P; ++i) { _R[i] = 0.0; }
_R[0] = 0.001;
_Xo = (float *)malloc((_P+1)*sizeof(float));
for (i = 0; i < _P; ++i) { _Xo[i] = 0.0; }
_ai = (float *)malloc((_P+1)*sizeof(float));
_whitened = (float*) malloc(sizeof(float)*_NumSamples);
}
void Whitening::Compute() {
int blocklen = 10000;
int i, newblocklen;
for(i=0;i<(int)_NumSamples;i=i+blocklen) {
if (i+blocklen >= (int)_NumSamples) {
newblocklen = _NumSamples -i - 1;
} else { newblocklen = blocklen; }
ComputeBlock(i, newblocklen);
}
}
void Whitening::ComputeBlock(int start, int blockSize) {
int i, j;
float alpha, E, ki;
float T = 8;
alpha = 1.0/T;
// calculate autocorrelation of current block
for (i = 0; i <= _P; ++i) {
float acc = 0;
for (j = 0; j < (int)blockSize; ++j) {
if (j >= i) {
acc += _pSamples[j+start] * _pSamples[j-i+start];
}
}
// smoothed update
_R[i] += alpha*(acc - _R[i]);
}
// calculate new filter coefficients
// Durbin's recursion, per p. 411 of Rabiner & Schafer 1978
E = _R[0];
for (i = 1; i <= _P; ++i) {
float sumalphaR = 0;
for (j = 1; j < i; ++j) {
sumalphaR += _ai[j]*_R[i-j];
}
ki = (_R[i] - sumalphaR)/E;
_ai[i] = ki;
for (j = 1; j <= i/2; ++j) {
float aj = _ai[j];
float aimj = _ai[i-j];
_ai[j] = aj - ki*aimj;
_ai[i-j] = aimj - ki*aj;
}
E = (1-ki*ki)*E;
}
// calculate new output
for (i = 0; i < (int)blockSize; ++i) {
float acc = _pSamples[i+start];
for (j = 1; j <= _P; ++j) {
if (i-j < 0) {
acc -= _ai[j]*_Xo[_P + i-j];
} else {
acc -= _ai[j]*_pSamples[i-j+start];
}
}
_whitened[i+start] = acc;
}
// save last few frames of input
for (i = 0; i <= _P; ++i) {
_Xo[i] = _pSamples[blockSize-1-_P+i+start];
}
}

View File

@ -0,0 +1,41 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#ifndef WHITENING_H
#define WHITENING_H
#include "Common.h"
#include "Params.h"
#include "MatrixUtility.h"
class AudioStreamInput;
class Whitening {
public:
inline Whitening() {};
Whitening(AudioStreamInput* pAudio);
Whitening(const float* pSamples, uint numSamples);
virtual ~Whitening();
void Compute();
void ComputeBlock(int start, int blockSize);
public:
float* getWhitenedSamples() const {return _whitened;}
inline uint getNumSamples() const {return _NumSamples;}
protected:
const float* _pSamples;
float* _whitened;
uint _NumSamples;
float* _R;
float *_Xo;
float *_ai;
int _P;
private:
void Init();
};
#endif

View File

@ -0,0 +1,22 @@
------------------------------
Building echoprint-codegen-ios
------------------------------
0. If you don't have it, get [boost](http://www.boost.org/)
**Note:** echoprint-codegen only uses boost headers for some numeric operations,
so you don't need to compile boost.
1. Open `echoprint-codegen-ios.xconfig` and set up your boost include directory, e.g.,
HEADER_SEARCH_PATHS = /Users/artgillespie/dev/src/boost_1_46_1
2. Build!
3. If you get a bunch of build errors, goto 2.
-------------------------------------------
Using echoprint-codegen-ios in your iOS app
-------------------------------------------
Check out the sample app!

View File

@ -0,0 +1,8 @@
//
// echoprint-codegen-ios.xcconfig
// echoprint-codegen-ios
//
// Created by Art Gillespie on 5/14/11.
//
HEADER_SEARCH_PATHS = /usr/local/include/boost-1_35

View File

@ -0,0 +1,303 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
9AE5AA19139FB2620009FCD8 /* SubbandAnalysis.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AE5AA17139FB2620009FCD8 /* SubbandAnalysis.h */; };
9AE5AA1A139FB2620009FCD8 /* SubbandAnalysis.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 9AE5AA18139FB2620009FCD8 /* SubbandAnalysis.cxx */; };
C8D4AD20137EE85F00CD506D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8D4AD1F137EE85F00CD506D /* Foundation.framework */; };
C8D4AD4F137EF41100CD506D /* AudioBufferInput.cxx in Sources */ = {isa = PBXBuildFile; fileRef = C8D4AD29137EF41100CD506D /* AudioBufferInput.cxx */; };
C8D4AD50137EF41100CD506D /* AudioBufferInput.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD2A137EF41100CD506D /* AudioBufferInput.h */; };
C8D4AD51137EF41100CD506D /* AudioStreamInput.cxx in Sources */ = {isa = PBXBuildFile; fileRef = C8D4AD2B137EF41100CD506D /* AudioStreamInput.cxx */; };
C8D4AD52137EF41100CD506D /* AudioStreamInput.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD2C137EF41100CD506D /* AudioStreamInput.h */; };
C8D4AD53137EF41100CD506D /* Base64.cxx in Sources */ = {isa = PBXBuildFile; fileRef = C8D4AD2D137EF41100CD506D /* Base64.cxx */; };
C8D4AD54137EF41100CD506D /* Base64.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD2E137EF41100CD506D /* Base64.h */; };
C8D4AD55137EF41100CD506D /* Codegen.cxx in Sources */ = {isa = PBXBuildFile; fileRef = C8D4AD2F137EF41100CD506D /* Codegen.cxx */; };
C8D4AD56137EF41100CD506D /* Codegen.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD30137EF41100CD506D /* Codegen.h */; };
C8D4AD57137EF41100CD506D /* Common.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD31137EF41100CD506D /* Common.h */; };
C8D4AD60137EF41100CD506D /* File.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD40137EF41100CD506D /* File.h */; };
C8D4AD62137EF41100CD506D /* Fingerprint.cxx in Sources */ = {isa = PBXBuildFile; fileRef = C8D4AD42137EF41100CD506D /* Fingerprint.cxx */; };
C8D4AD63137EF41100CD506D /* Fingerprint.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD43137EF41100CD506D /* Fingerprint.h */; };
C8D4AD64137EF41100CD506D /* MatrixUtility.cxx in Sources */ = {isa = PBXBuildFile; fileRef = C8D4AD44137EF41100CD506D /* MatrixUtility.cxx */; };
C8D4AD65137EF41100CD506D /* MatrixUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD45137EF41100CD506D /* MatrixUtility.h */; };
C8D4AD68137EF41100CD506D /* Params.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD48137EF41100CD506D /* Params.h */; };
C8D4AD6D137EF41100CD506D /* win_funcs.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD4D137EF41100CD506D /* win_funcs.h */; };
C8D4AD6E137EF41100CD506D /* win_unistd.h in Headers */ = {isa = PBXBuildFile; fileRef = C8D4AD4E137EF41100CD506D /* win_unistd.h */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
9AE5AA17139FB2620009FCD8 /* SubbandAnalysis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SubbandAnalysis.h; path = ../SubbandAnalysis.h; sourceTree = SOURCE_ROOT; };
9AE5AA18139FB2620009FCD8 /* SubbandAnalysis.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SubbandAnalysis.cxx; path = ../SubbandAnalysis.cxx; sourceTree = SOURCE_ROOT; };
C8D4AD1C137EE85F00CD506D /* libechoprint-codegen-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libechoprint-codegen-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; };
C8D4AD1F137EE85F00CD506D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
C8D4AD23137EE85F00CD506D /* echoprint-codegen-ios-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "echoprint-codegen-ios-Prefix.pch"; sourceTree = "<group>"; };
C8D4AD29137EF41100CD506D /* AudioBufferInput.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AudioBufferInput.cxx; path = ../../AudioBufferInput.cxx; sourceTree = "<group>"; };
C8D4AD2A137EF41100CD506D /* AudioBufferInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AudioBufferInput.h; path = ../../AudioBufferInput.h; sourceTree = "<group>"; };
C8D4AD2B137EF41100CD506D /* AudioStreamInput.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AudioStreamInput.cxx; path = ../../AudioStreamInput.cxx; sourceTree = "<group>"; };
C8D4AD2C137EF41100CD506D /* AudioStreamInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AudioStreamInput.h; path = ../../AudioStreamInput.h; sourceTree = "<group>"; };
C8D4AD2D137EF41100CD506D /* Base64.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Base64.cxx; path = ../../Base64.cxx; sourceTree = "<group>"; };
C8D4AD2E137EF41100CD506D /* Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Base64.h; path = ../../Base64.h; sourceTree = "<group>"; };
C8D4AD2F137EF41100CD506D /* Codegen.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Codegen.cxx; path = ../../Codegen.cxx; sourceTree = "<group>"; };
C8D4AD30137EF41100CD506D /* Codegen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Codegen.h; path = ../../Codegen.h; sourceTree = "<group>"; };
C8D4AD31137EF41100CD506D /* Common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Common.h; path = ../../Common.h; sourceTree = "<group>"; };
C8D4AD40137EF41100CD506D /* File.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = File.h; path = ../../File.h; sourceTree = "<group>"; };
C8D4AD42137EF41100CD506D /* Fingerprint.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Fingerprint.cxx; path = ../../Fingerprint.cxx; sourceTree = "<group>"; };
C8D4AD43137EF41100CD506D /* Fingerprint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Fingerprint.h; path = ../../Fingerprint.h; sourceTree = "<group>"; };
C8D4AD44137EF41100CD506D /* MatrixUtility.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MatrixUtility.cxx; path = ../../MatrixUtility.cxx; sourceTree = "<group>"; };
C8D4AD45137EF41100CD506D /* MatrixUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MatrixUtility.h; path = ../../MatrixUtility.h; sourceTree = "<group>"; };
C8D4AD48137EF41100CD506D /* Params.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Params.h; path = ../../Params.h; sourceTree = "<group>"; };
C8D4AD4D137EF41100CD506D /* win_funcs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = win_funcs.h; path = ../../win_funcs.h; sourceTree = "<group>"; };
C8D4AD4E137EF41100CD506D /* win_unistd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = win_unistd.h; path = ../../win_unistd.h; sourceTree = "<group>"; };
C8D4AD6F137EF5AC00CD506D /* echoprint-codegen-ios.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "echoprint-codegen-ios.xcconfig"; sourceTree = "<group>"; };
C8D4AD70137EF68F00CD506D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
C8D4AD19137EE85F00CD506D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C8D4AD20137EE85F00CD506D /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
C8D4AD11137EE85F00CD506D = {
isa = PBXGroup;
children = (
C8D4AD70137EF68F00CD506D /* README.md */,
C8D4AD6F137EF5AC00CD506D /* echoprint-codegen-ios.xcconfig */,
C8D4AD21137EE85F00CD506D /* echoprint-codegen-ios */,
C8D4AD1E137EE85F00CD506D /* Frameworks */,
C8D4AD1D137EE85F00CD506D /* Products */,
);
sourceTree = "<group>";
};
C8D4AD1D137EE85F00CD506D /* Products */ = {
isa = PBXGroup;
children = (
C8D4AD1C137EE85F00CD506D /* libechoprint-codegen-ios.a */,
);
name = Products;
sourceTree = "<group>";
};
C8D4AD1E137EE85F00CD506D /* Frameworks */ = {
isa = PBXGroup;
children = (
C8D4AD1F137EE85F00CD506D /* Foundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
C8D4AD21137EE85F00CD506D /* echoprint-codegen-ios */ = {
isa = PBXGroup;
children = (
9AE5AA17139FB2620009FCD8 /* SubbandAnalysis.h */,
9AE5AA18139FB2620009FCD8 /* SubbandAnalysis.cxx */,
C8D4AD29137EF41100CD506D /* AudioBufferInput.cxx */,
C8D4AD2A137EF41100CD506D /* AudioBufferInput.h */,
C8D4AD2B137EF41100CD506D /* AudioStreamInput.cxx */,
C8D4AD2C137EF41100CD506D /* AudioStreamInput.h */,
C8D4AD2D137EF41100CD506D /* Base64.cxx */,
C8D4AD2E137EF41100CD506D /* Base64.h */,
C8D4AD2F137EF41100CD506D /* Codegen.cxx */,
C8D4AD30137EF41100CD506D /* Codegen.h */,
C8D4AD31137EF41100CD506D /* Common.h */,
C8D4AD3A137EF41100CD506D /* fft */,
C8D4AD40137EF41100CD506D /* File.h */,
C8D4AD42137EF41100CD506D /* Fingerprint.cxx */,
C8D4AD43137EF41100CD506D /* Fingerprint.h */,
C8D4AD44137EF41100CD506D /* MatrixUtility.cxx */,
C8D4AD45137EF41100CD506D /* MatrixUtility.h */,
C8D4AD48137EF41100CD506D /* Params.h */,
C8D4AD4D137EF41100CD506D /* win_funcs.h */,
C8D4AD4E137EF41100CD506D /* win_unistd.h */,
C8D4AD22137EE85F00CD506D /* Supporting Files */,
);
path = "echoprint-codegen-ios";
sourceTree = "<group>";
};
C8D4AD22137EE85F00CD506D /* Supporting Files */ = {
isa = PBXGroup;
children = (
C8D4AD23137EE85F00CD506D /* echoprint-codegen-ios-Prefix.pch */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
C8D4AD3A137EF41100CD506D /* fft */ = {
isa = PBXGroup;
children = (
);
name = fft;
path = ../../fft;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
C8D4AD1A137EE85F00CD506D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
C8D4AD50137EF41100CD506D /* AudioBufferInput.h in Headers */,
C8D4AD52137EF41100CD506D /* AudioStreamInput.h in Headers */,
C8D4AD54137EF41100CD506D /* Base64.h in Headers */,
C8D4AD56137EF41100CD506D /* Codegen.h in Headers */,
C8D4AD57137EF41100CD506D /* Common.h in Headers */,
C8D4AD60137EF41100CD506D /* File.h in Headers */,
C8D4AD63137EF41100CD506D /* Fingerprint.h in Headers */,
C8D4AD65137EF41100CD506D /* MatrixUtility.h in Headers */,
C8D4AD68137EF41100CD506D /* Params.h in Headers */,
C8D4AD6D137EF41100CD506D /* win_funcs.h in Headers */,
C8D4AD6E137EF41100CD506D /* win_unistd.h in Headers */,
9AE5AA19139FB2620009FCD8 /* SubbandAnalysis.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
C8D4AD1B137EE85F00CD506D /* echoprint-codegen-ios */ = {
isa = PBXNativeTarget;
buildConfigurationList = C8D4AD26137EE85F00CD506D /* Build configuration list for PBXNativeTarget "echoprint-codegen-ios" */;
buildPhases = (
C8D4AD18137EE85F00CD506D /* Sources */,
C8D4AD19137EE85F00CD506D /* Frameworks */,
C8D4AD1A137EE85F00CD506D /* Headers */,
);
buildRules = (
);
dependencies = (
);
name = "echoprint-codegen-ios";
productName = "echoprint-codegen-ios";
productReference = C8D4AD1C137EE85F00CD506D /* libechoprint-codegen-ios.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
C8D4AD13137EE85F00CD506D /* Project object */ = {
isa = PBXProject;
buildConfigurationList = C8D4AD16137EE85F00CD506D /* Build configuration list for PBXProject "echoprint-codegen-ios" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = C8D4AD11137EE85F00CD506D;
productRefGroup = C8D4AD1D137EE85F00CD506D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
C8D4AD1B137EE85F00CD506D /* echoprint-codegen-ios */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
C8D4AD18137EE85F00CD506D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C8D4AD4F137EF41100CD506D /* AudioBufferInput.cxx in Sources */,
C8D4AD51137EF41100CD506D /* AudioStreamInput.cxx in Sources */,
C8D4AD53137EF41100CD506D /* Base64.cxx in Sources */,
C8D4AD55137EF41100CD506D /* Codegen.cxx in Sources */,
C8D4AD62137EF41100CD506D /* Fingerprint.cxx in Sources */,
C8D4AD64137EF41100CD506D /* MatrixUtility.cxx in Sources */,
9AE5AA1A139FB2620009FCD8 /* SubbandAnalysis.cxx in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
C8D4AD24137EE85F00CD506D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C8D4AD6F137EF5AC00CD506D /* echoprint-codegen-ios.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = DEBUG;
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_VERSION = com.apple.compilers.llvmgcc42;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
SDKROOT = iphoneos;
};
name = Debug;
};
C8D4AD25137EE85F00CD506D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C8D4AD6F137EF5AC00CD506D /* echoprint-codegen-ios.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_VERSION = com.apple.compilers.llvmgcc42;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 4.2;
SDKROOT = iphoneos;
};
name = Release;
};
C8D4AD27137EE85F00CD506D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
DSTROOT = /tmp/echoprint_codegen_ios.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "echoprint-codegen-ios/echoprint-codegen-ios-Prefix.pch";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
C8D4AD28137EE85F00CD506D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
DSTROOT = /tmp/echoprint_codegen_ios.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "echoprint-codegen-ios/echoprint-codegen-ios-Prefix.pch";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C8D4AD16137EE85F00CD506D /* Build configuration list for PBXProject "echoprint-codegen-ios" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C8D4AD24137EE85F00CD506D /* Debug */,
C8D4AD25137EE85F00CD506D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C8D4AD26137EE85F00CD506D /* Build configuration list for PBXNativeTarget "echoprint-codegen-ios" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C8D4AD27137EE85F00CD506D /* Debug */,
C8D4AD28137EE85F00CD506D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = C8D4AD13137EE85F00CD506D /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:echoprint-codegen-ios.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,4 @@
//
// Prefix header for all source files of the 'echoprint-codegen-ios' target in the 'echoprint-codegen-ios' project
//

298
3rdparty/echoprint-codegen/src/main.cxx vendored Normal file
View File

@ -0,0 +1,298 @@
//
// echoprint-codegen
// Copyright 2011 The Echo Nest Corporation. All rights reserved.
//
#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>
#define MAX_FILES 200000
#ifdef _WIN32
#include <pthread.h>
#endif
// Struct to pass to the worker threads
typedef struct {
char *filename;
int start_offset;
int duration;
int done ;
int tag;
char *output;
} thread_parm_t;
// Thank you http://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine
#ifdef _WIN32
#include <windows.h>
#elif MACOS
#include <sys/param.h>
#include <sys/sysctl.h>
#else
#include <unistd.h>
#endif
int getNumCores() {
#ifdef WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
#elif MACOS
int nm[2];
size_t len = 4;
uint32_t count;
nm[0] = CTL_HW; nm[1] = HW_AVAILCPU;
sysctl(nm, 2, &count, &len, NULL, 0);
if(count < 1) {
nm[1] = HW_NCPU;
sysctl(nm, 2, &count, &len, NULL, 0);
if(count < 1) { count = 1; }
}
return count;
#else
return sysconf(_SC_NPROCESSORS_ONLN);
#endif
}
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 (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;
}
char* json_string_for_file(char* filename, int start_offset, int duration, int tag) {
// Given a filename, do all the work to get a JSON string output.
// This is called by a thread
double t1 = now();
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());
return output;
}
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());
return output;
}
t1 = now() - t1;
double t2 = now();
auto_ptr<Codegen> pCodegen(new Codegen(pAudio->getSamples(), numSamples, start_offset));
t2 = now() - t2;
// Get the ID3 tag information.
auto_ptr<Metadata> pMetadata(new Metadata(filename));
// preamble + codelen
char* output = (char*) malloc(sizeof(char)*(16384 + strlen(pCodegen->getCodeString().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(filename).c_str(),
numSamples,
duration,
start_offset,
pCodegen->getVersion(),
t2,
t1,
pCodegen->getNumCodes(),
pCodegen->getCodeString().c_str(),
tag
);
return output;
}
void *threaded_json_string_for_file(void *parm) {
// pthread stub to invoke json_string_for_file
thread_parm_t *p = (thread_parm_t *)parm;
char * output = json_string_for_file(p->filename, p->start_offset, p->duration, p->tag);
p->output = output;
// mark when we're done so the controlling thread can move on.
p->done = 1;
return NULL;
}
void print_json_to_screen(char* output, int count, int done) {
// Print a json block depending on how many there are and where we are.
if(done==1 && count>1) printf("[\n%s,\n", output);
else if(done==1 && count == 1) printf("[\n%s\n]\n", output);
else if(done == count) printf("%s\n]\n", output);
else printf("%s,\n", output);
}
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s [ filename | -s ] [seconds_start] [seconds_duration] [< file_list (if -s is set)]\n", argv[0]);
exit(-1);
}
try {
string files[MAX_FILES];
char *filename = argv[1];
int count = 0;
int start_offset = 0;
int duration = 0;
int already = 0;
if (argc > 2) start_offset = atoi(argv[2]);
if (argc > 3) duration = atoi(argv[3]);
if (argc > 4) already = atoi(argv[4]);
// If you give it -s, it means to read in a list of files from stdin.
if (strcmp(filename, "-s") == 0) {
while(cin) {
if (count < MAX_FILES) {
string temp_str;
getline(cin, temp_str);
if (temp_str.size() > 2)
files[count++] = temp_str;
} else {
throw std::runtime_error("Too many files on stdin to process\n");
}
}
} else files[count++] = filename;
if(count == 0) throw std::runtime_error("No files given.\n");
#ifdef _WIN32
//#ifdef __APPLE__
fprintf(stderr, "no thread mode\n");
// Threading doesn't work in windows yet.
for(int i=0;i<count;i++) {
char* output = json_string_for_file((char*)files[i].c_str(), start_offset, duration, i);
print_json_to_screen(output, count, i);
free(output);
}
return 1;
#endif
// FIgure out how many threads to use based on # of cores
int num_threads = getNumCores();
if (num_threads > 8) num_threads = 8;
if (num_threads < 2) num_threads = 2;
if (num_threads > count) num_threads = count;
// Setup threading
pthread_t *t = (pthread_t*)malloc(sizeof(pthread_t)*num_threads);
thread_parm_t **parm = (thread_parm_t**)malloc(sizeof(thread_parm_t*)*num_threads);
pthread_attr_t *attr = (pthread_attr_t*)malloc(sizeof(pthread_attr_t)*num_threads);
// Kick off the first N threads
int still_left = count-1-already;
for(int i=0;i<num_threads;i++) {
parm[i] = (thread_parm_t *)malloc(sizeof(thread_parm_t));
parm[i]->filename = (char*)files[still_left].c_str();
parm[i]->start_offset = start_offset;
parm[i]->tag = still_left;
parm[i]->duration = duration;
parm[i]->done = 0;
still_left--;
pthread_attr_init(&attr[i]);
pthread_attr_setdetachstate(&attr[i], PTHREAD_CREATE_DETACHED);
// Kick off the thread
if (pthread_create(&t[i], &attr[i], threaded_json_string_for_file, (void*)parm[i]))
throw std::runtime_error("Problem creating thread\n");
}
int done = 0;
// Now wait for the threads to come back, and also kick off new ones
while(done<count) {
// Check which threads are done
for(int i=0;i<num_threads;i++) {
if (parm[i]->done) {
parm[i]->done = 0;
done++;
print_json_to_screen(parm[i]->output, count, done);
free(parm[i]->output);
// More to do? Start a new one on this just finished thread
if(still_left >= 0) {
parm[i]->tag = still_left;
parm[i]->filename = (char*)files[still_left].c_str();
still_left--;
int err= pthread_create(&t[i], &attr[i], threaded_json_string_for_file, (void*)parm[i]);
if(err)
throw std::runtime_error("Problem creating thread\n");
}
}
}
}
// Clean up threads
for(int i=0;i<num_threads;i++) {
free(parm[i]);
}
free(t);
free(parm);
free(attr);
return 0;
}
catch(std::runtime_error& ex) {
fprintf(stderr, "%s\n", ex.what());
return 1;
}
catch(...) {
fprintf(stderr, "Unknown failure occurred\n");
return 2;
}
}

View File

@ -0,0 +1,34 @@
#ifndef __WIN_FUNCS_H
#define __WIN_FUNCS_H 1
#include <math.h>
#include <winsock.h>
#include <sys/timeb.h>
#define ROUND_FUNC(type,suff) inline type round##suff(type x) \
{ \
if (x >= 0.0##suff){ \
type y = floor##suff(x); \
if (x - y >= 0.5##suff) \
y += 1.0##suff; \
return y; \
}else{ \
type y = ceil##suff(x); \
if (y - x >= 0.5##suff) \
y -= 1.0##suff; \
return y; \
} \
}
ROUND_FUNC(float,f)
ROUND_FUNC(double,)
inline void gettimeofday(struct timeval* t,void* timezone)
{ struct _timeb timebuffer;
_ftime( &timebuffer );
t->tv_sec=timebuffer.time;
t->tv_usec=1000*timebuffer.millitm;
}
#endif /* __WIN_FUNCS_H */

View File

@ -0,0 +1,36 @@
#ifndef _UNISTD_H
#define _UNISTD_H 1
/* This file intended to serve as a drop-in replacement for
* unistd.h on Windows
* Please add functionality as neeeded
http://stackoverflow.com/questions/341817/is-there-a-replacement-for-unistd-h-for-windows-visual-c
*/
#include <stdlib.h>
#include <io.h>
/*
#include <getopt.h>
getopt from: http://www.pwilson.net/sample.html. */
#define srandom srand
#define random rand
const int F_OK = 0;
const int W_OK = 2;
const int R_OK = 4;
#define access _access
#define ftruncate _chsize
/* stdio */
#define popen _popen
#define pclose _pclose
/* float.h */
#define finite _finite
#define ssize_t int
#endif /* unistd.h */

View File

@ -365,6 +365,7 @@ add_subdirectory(src)
if (WIN32)
add_subdirectory(3rdparty/qtwin)
endif (WIN32)
add_subdirectory(3rdparty/echoprint-codegen)
add_subdirectory(3rdparty/universalchardet)
add_subdirectory(tests)
add_subdirectory(dist)

View File

@ -130,6 +130,7 @@ set(SOURCES
library/librarywatcher.cpp
library/sqlrow.cpp
musicbrainz/echoprinter.cpp
musicbrainz/fingerprinter.cpp
musicbrainz/musicbrainzclient.cpp
musicbrainz/musicdnsclient.cpp
@ -989,6 +990,7 @@ add_dependencies(clementine_lib pot)
target_link_libraries(clementine_lib
chardet
echoprint
${ECHONEST_LIBRARIES}
${GOBJECT_LIBRARIES}
${GLIB_LIBRARIES}

View File

@ -0,0 +1,213 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "echoprinter.h"
#include "core/logging.h"
#include "core/timeconstants.h"
#include "3rdparty/echoprint-codegen/src/Codegen.h"
#include <QDir>
#include <QEventLoop>
#include <QtDebug>
#include <QTime>
Echoprinter::Echoprinter(const QString& filename)
: filename_(filename),
event_loop_(NULL),
convert_element_(NULL),
finishing_(false)
{
buffer_.open(QIODevice::WriteOnly);
}
Echoprinter::~Echoprinter() {
}
GstElement* Echoprinter::CreateElement(const QString &factory_name,
GstElement *bin) {
GstElement* ret = gst_element_factory_make(
factory_name.toAscii().constData(),
factory_name.toAscii().constData());
if (ret && bin)
gst_bin_add(GST_BIN(bin), ret);
if (!ret) {
qLog(Warning) << "Couldn't create the gstreamer element" << factory_name;
}
return ret;
}
QString Echoprinter::CreateFingerprint() {
GMainContext* context = g_main_context_new();
g_main_context_push_thread_default(context);
event_loop_ = g_main_loop_new(context, FALSE);
pipeline_ = gst_pipeline_new("pipeline");
GstElement* src = CreateElement("filesrc", pipeline_);
GstElement* decode = CreateElement("decodebin2", pipeline_);
GstElement* convert = CreateElement("audioconvert", pipeline_);
GstElement* resample = CreateElement("audioresample", pipeline_);
GstElement* sink = CreateElement("appsink", pipeline_);
if (!src || !decode || !convert || !resample || !sink) {
return QString();
}
convert_element_ = convert;
// Connect the elements
gst_element_link_many(src, decode, NULL);
gst_element_link_many(convert, resample, NULL);
// Echoprint expects mono floats at a sample rate of 11025Hz.
GstCaps* caps = gst_caps_new_simple(
"audio/x-raw-float",
"width", G_TYPE_INT, 32,
"channels", G_TYPE_INT, 1,
"rate", G_TYPE_INT, 11025,
NULL);
gst_element_link_filtered(resample, sink, caps);
gst_caps_unref(caps);
GstAppSinkCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.new_buffer = NewBufferCallback;
gst_app_sink_set_callbacks(reinterpret_cast<GstAppSink*>(sink), &callbacks, this, NULL);
g_object_set(G_OBJECT(sink), "sync", FALSE, NULL);
g_object_set(G_OBJECT(sink), "emit-signals", TRUE, NULL);
// Set the filename
g_object_set(src, "location", filename_.toLocal8Bit().constData(), NULL);
// Connect signals
g_signal_connect(decode, "new-decoded-pad", G_CALLBACK(NewPadCallback), this);
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallbackSync, this);
guint bus_callback_id = gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallback, this);
QTime time;
time.start();
// Start playing
gst_element_set_state(pipeline_, GST_STATE_PLAYING);
g_main_loop_run(event_loop_);
g_main_loop_unref(event_loop_);
g_main_context_unref(context);
int decode_time = time.restart();
buffer_.close();
QByteArray data = buffer_.data();
Codegen codegen(reinterpret_cast<const float*>(data.constData()),
data.size() / sizeof(float), 0);
QString fingerprint = QString::fromAscii(codegen.getCodeString().c_str());
int codegen_time = time.elapsed();
qLog(Debug) << "Decode time:" << decode_time << "Codegen time:" << codegen_time;
qLog(Debug) << "Echoprint:" << fingerprint;
// Cleanup
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), NULL, NULL);
g_source_remove(bus_callback_id);
gst_object_unref(pipeline_);
return fingerprint;
}
void Echoprinter::NewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer data) {
Echoprinter* instance = reinterpret_cast<Echoprinter*>(data);
GstPad* const audiopad = gst_element_get_pad(instance->convert_element_, "sink");
if (GST_PAD_IS_LINKED(audiopad)) {
qLog(Warning) << "audiopad is already linked, unlinking old pad";
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
}
gst_pad_link(pad, audiopad);
gst_object_unref(audiopad);
}
void Echoprinter::ReportError(GstMessage* msg) {
GError* error;
gchar* debugs;
gst_message_parse_error(msg, &error, &debugs);
QString message = QString::fromLocal8Bit(error->message);
g_error_free(error);
free(debugs);
qLog(Error) << "Error processing" << filename_ << ":" << message;
}
gboolean Echoprinter::BusCallback(GstBus*, GstMessage* msg, gpointer data) {
Echoprinter* instance = reinterpret_cast<Echoprinter*>(data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
instance->ReportError(msg);
g_main_loop_quit(instance->event_loop_);
break;
default:
break;
}
return GST_BUS_DROP;
}
GstBusSyncReply Echoprinter::BusCallbackSync(GstBus*, GstMessage* msg, gpointer data) {
Echoprinter* instance = reinterpret_cast<Echoprinter*>(data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_main_loop_quit(instance->event_loop_);
break;
case GST_MESSAGE_ERROR:
instance->ReportError(msg);
g_main_loop_quit(instance->event_loop_);
break;
default:
break;
}
return GST_BUS_PASS;
}
GstFlowReturn Echoprinter::NewBufferCallback(GstAppSink* app_sink, gpointer self) {
Echoprinter* me = reinterpret_cast<Echoprinter*>(self);
if (me->finishing_) {
return GST_FLOW_OK;
}
GstBuffer* buffer = gst_app_sink_pull_buffer(app_sink);
me->buffer_.write(reinterpret_cast<const char*>(buffer->data), buffer->size);
gst_buffer_unref(buffer);
gint64 pos = 0;
GstFormat format = GST_FORMAT_TIME;
gboolean ret = gst_element_query_position(me->pipeline_, &format, &pos);
if (ret && pos > 30 * kNsecPerSec) {
me->finishing_ = true;
g_main_loop_quit(me->event_loop_);
}
return GST_FLOW_OK;
}

View File

@ -0,0 +1,68 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ECHOPRINTER_H
#define ECHOPRINTER_H
#include <glib.h>
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
#include <QBuffer>
#include <QString>
class QEventLoop;
class Echoprinter {
// Creates an Echoprint fingerprint from a song.
// Uses GStreamer to open and decode the file as PCM data and passes this
// to Echoprint's code generator. The generated code can be used to identify
// a song via Echonest's identify method.
// You should create one Echoprinter for each file you want to fingerprint.
// This class works well with QtConcurrentMap.
public:
Echoprinter(const QString& filename);
~Echoprinter();
// Creates a fingerprint from the song. This method is blocking, so you want
// to call it in another thread. Returns an empty string if no fingerprint
// could be created.
QString CreateFingerprint();
private:
GstElement* CreateElement(const QString& factory_name, GstElement* bin = NULL);
void ReportError(GstMessage* message);
static void NewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer data);
static gboolean BusCallback(GstBus*, GstMessage* msg, gpointer data);
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg, gpointer data);
static GstFlowReturn NewBufferCallback(GstAppSink* app_sink, gpointer self);
private:
QString filename_;
GMainLoop* event_loop_;
GstElement* convert_element_;
GstElement* pipeline_;
QBuffer buffer_;
bool finishing_;
};
#endif // ECHOPRINTER_H