Commit f1c24d9c authored by Stefan Westerfeld's avatar Stefan Westerfeld

Split up audiowmark.cc into separate files.

- wmadd.cc - code to generate the watermark
- wmget.cc - code to detect the watermark
- wmcommon.cc/hh - code shared by add/get
- audiowmark.cc - remaining code
Signed-off-by: Stefan Westerfeld's avatarStefan Westerfeld <stefan@space.twc.de>
parent fb503349
......@@ -6,7 +6,7 @@ COMMON_SRC = utils.hh utils.cc convcode.hh convcode.cc random.hh random.cc mp3.c
rawconverter.cc rawconverter.hh mp3inputstream.cc mp3inputstream.hh
COMMON_LIBS = $(SNDFILE_LIBS) $(FFTW_LIBS) $(LIBGCRYPT_LIBS) $(LIBMPG123_LIBS)
audiowmark_SOURCES = audiowmark.cc fft.cc fft.hh $(COMMON_SRC)
audiowmark_SOURCES = audiowmark.cc fft.cc fft.hh wmget.cc wmadd.cc wmcommon.hh wmcommon.cc $(COMMON_SRC)
audiowmark_LDFLAGS = $(COMMON_LIBS)
noinst_PROGRAMS = testconvcode testrandom testmp3 teststream
......
......@@ -5,4 +5,4 @@ streaming:
- need explicit Error objects
- logging
- raw streams
- mp3 indexing
- mp3 indexing / detect (resync-limit)
This diff is collapsed.
This diff is collapsed.
#include "wmcommon.hh"
#include "fft.hh"
#include "convcode.hh"
int Params::frames_per_bit = 2;
double Params::water_delta = 0.01;
bool Params::mix = true;
bool Params::hard = false; // hard decode bits? (soft decoding is better)
bool Params::snr = false; // compute/show snr while adding watermark
int Params::have_key = 0;
int Params::test_cut = 0; // for sync test
bool Params::test_no_sync = false; // disable sync
int Params::test_truncate = 0;
Format Params::input_format = Format::AUTO;
Format Params::output_format = Format::AUTO;
RawFormat Params::raw_input_format;
RawFormat Params::raw_output_format;
using std::vector;
using std::complex;
inline double
window_cos (double x) /* von Hann window */
{
if (fabs (x) > 1)
return 0;
return 0.5 * cos (x * M_PI) + 0.5;
}
inline double
window_hamming (double x) /* sharp (rectangle) cutoffs at boundaries */
{
if (fabs (x) > 1)
return 0;
return 0.54 + 0.46 * cos (M_PI * x);
}
/*
* glibc log2f is a lot faster than glibc log10
*/
inline double
fast_log10 (double l)
{
constexpr double log2_log10_factor = 0.3010299956639811952; // 1 / log2 (10)
return log2f (l) * log2_log10_factor;
}
double
db_from_factor (double factor, double min_dB)
{
if (factor > 0)
{
double dB = fast_log10 (factor); /* Bell */
dB *= 20;
return dB;
}
else
return min_dB;
}
FFTAnalyzer::FFTAnalyzer (int n_channels) :
m_n_channels (n_channels)
{
/* generate analysis window */
m_window.resize (Params::frame_size);
double window_weight = 0;
for (size_t i = 0; i < Params::frame_size; i++)
{
const double fsize_2 = Params::frame_size / 2.0;
// const double win = window_cos ((i - fsize_2) / fsize_2);
const double win = window_hamming ((i - fsize_2) / fsize_2);
//const double win = 1;
m_window[i] = win;
window_weight += win;
}
/* normalize window using window weight */
for (size_t i = 0; i < Params::frame_size; i++)
{
m_window[i] *= 2.0 / window_weight;
}
/* allocate properly aligned buffers for SIMD */
m_frame = new_array_float (Params::frame_size);
m_frame_fft = new_array_float (Params::frame_size);
}
FFTAnalyzer::~FFTAnalyzer()
{
free_array_float (m_frame);
free_array_float (m_frame_fft);
}
vector<vector<complex<float>>>
FFTAnalyzer::run_fft (const vector<float>& samples, size_t start_index)
{
assert (samples.size() >= (Params::frame_size + start_index) * m_n_channels);
vector<vector<complex<float>>> fft_out;
for (int ch = 0; ch < m_n_channels; ch++)
{
size_t pos = start_index * m_n_channels + ch;
assert (pos + (Params::frame_size - 1) * m_n_channels < samples.size());
/* deinterleave frame data and apply window */
for (size_t x = 0; x < Params::frame_size; x++)
{
m_frame[x] = samples[pos] * m_window[x];
pos += m_n_channels;
}
/* FFT transform */
fftar_float (Params::frame_size, m_frame, m_frame_fft);
/* complex<float> and frame_fft have the same layout in memory */
const complex<float> *first = (complex<float> *) m_frame_fft;
const complex<float> *last = first + Params::frame_size / 2 + 1;
fft_out.emplace_back (first, last);
}
return fft_out;
}
vector<vector<complex<float>>>
FFTAnalyzer::fft_range (const vector<float>& samples, size_t start_index, size_t frame_count)
{
vector<vector<complex<float>>> fft_out;
/* if there is not enough space for frame_count values, return an error (empty vector) */
if (samples.size() < (start_index + frame_count * Params::frame_size) * m_n_channels)
return fft_out;
for (size_t f = 0; f < frame_count; f++)
{
const size_t frame_start = (f * Params::frame_size) + start_index;
vector<vector<complex<float>>> frame_result = run_fft (samples, frame_start);
for (auto& fr : frame_result)
fft_out.emplace_back (std::move (fr));
}
return fft_out;
}
int
frame_pos (int f, bool sync)
{
static vector<int> pos_vec;
if (pos_vec.empty())
{
int frame_count = mark_data_frame_count() + mark_sync_frame_count();
for (int i = 0; i < frame_count; i++)
pos_vec.push_back (i);
Random random (0, Random::Stream::frame_position);
random.shuffle (pos_vec);
}
if (sync)
{
assert (f >= 0 && size_t (f) < mark_sync_frame_count());
return pos_vec[f];
}
else
{
assert (f >= 0 && size_t (f) < mark_data_frame_count());
return pos_vec[f + mark_sync_frame_count()];
}
}
int
sync_frame_pos (int f)
{
return frame_pos (f, true);
}
int
data_frame_pos (int f)
{
return frame_pos (f, false);
}
size_t
mark_data_frame_count()
{
return conv_code_size (ConvBlockType::a, Params::payload_size) * Params::frames_per_bit;
}
size_t
mark_sync_frame_count()
{
return Params::sync_bits * Params::sync_frames_per_bit;
}
vector<MixEntry>
gen_mix_entries()
{
const int frame_count = mark_data_frame_count();
vector<MixEntry> mix_entries (frame_count * Params::bands_per_frame);
UpDownGen up_down_gen (Random::Stream::data_up_down);
int entry = 0;
for (int f = 0; f < frame_count; f++)
{
const int index = data_frame_pos (f);
UpDownArray up, down;
up_down_gen.get (f, up, down);
assert (up.size() == down.size());
for (size_t i = 0; i < up.size(); i++)
mix_entries[entry++] = { index, up[i], down[i] };
}
Random random (/* seed */ 0, Random::Stream::mix);
random.shuffle (mix_entries);
return mix_entries;
}
#ifndef AUDIOWMARK_WM_COMMON_HH
#define AUDIOWMARK_WM_COMMON_HH
#include <array>
#include <complex>
#include "random.hh"
#include "rawinputstream.hh"
#include <assert.h>
enum class Format { AUTO = 1, RAW = 2 };
class Params
{
public:
static constexpr size_t frame_size = 1024;
static int frames_per_bit;
static constexpr size_t bands_per_frame = 30;
static constexpr int max_band = 100;
static constexpr int min_band = 20;
static double water_delta;
static bool mix;
static bool hard; // hard decode bits? (soft decoding is better)
static bool snr; // compute/show snr while adding watermark
static int have_key;
static constexpr size_t payload_size = 128; // number of payload bits for the watermark
static constexpr int sync_bits = 6;
static constexpr int sync_frames_per_bit = 85;
static constexpr int sync_search_step = 256;
static constexpr int sync_search_fine = 8;
static constexpr double sync_threshold2 = 0.7; // minimum refined quality
static constexpr size_t frames_pad_start = 250; // padding at start, in case track starts with silence
static constexpr int mark_sample_rate = 44100; // watermark generation and detection sample rate
static int test_cut; // for sync test
static bool test_no_sync;
static int test_truncate;
static Format input_format;
static Format output_format;
static RawFormat raw_input_format;
static RawFormat raw_output_format;
};
typedef std::array<int, 30> UpDownArray;
class UpDownGen
{
Random::Stream random_stream;
Random random;
std::vector<int> bands_reorder;
public:
UpDownGen (Random::Stream random_stream) :
random_stream (random_stream),
random (0, random_stream),
bands_reorder (Params::max_band - Params::min_band + 1)
{
UpDownArray x;
assert (x.size() == Params::bands_per_frame);
}
void
get (int f, UpDownArray& up, UpDownArray& down)
{
for (size_t i = 0; i < bands_reorder.size(); i++)
bands_reorder[i] = Params::min_band + i;
random.seed (f, random_stream); // use per frame random seed
random.shuffle (bands_reorder);
assert (2 * Params::bands_per_frame < bands_reorder.size());
for (size_t i = 0; i < Params::bands_per_frame; i++)
{
up[i] = bands_reorder[i];
down[i] = bands_reorder[Params::bands_per_frame + i];
}
}
};
class FFTAnalyzer
{
int m_n_channels = 0;
std::vector<float> m_window;
float *m_frame = nullptr;
float *m_frame_fft = nullptr;
public:
FFTAnalyzer (int n_channels);
~FFTAnalyzer();
std::vector<std::vector<std::complex<float>>> run_fft (const std::vector<float>& samples, size_t start_index);
std::vector<std::vector<std::complex<float>>> fft_range (const std::vector<float>& samples, size_t start_index, size_t frame_count);
};
struct MixEntry
{
int frame;
int up;
int down;
};
std::vector<MixEntry> gen_mix_entries();
double db_from_factor (double factor, double min_dB);
size_t mark_data_frame_count();
size_t mark_sync_frame_count();
int sync_frame_pos (int f);
int data_frame_pos (int f);
template<class T> std::vector<T>
randomize_bit_order (const std::vector<T>& bit_vec, bool encode)
{
std::vector<unsigned int> order;
for (size_t i = 0; i < bit_vec.size(); i++)
order.push_back (i);
Random random (/* seed */ 0, Random::Stream::bit_order);
random.shuffle (order);
std::vector<T> out_bits (bit_vec.size());
for (size_t i = 0; i < bit_vec.size(); i++)
{
if (encode)
out_bits[i] = bit_vec[order[i]];
else
out_bits[order[i]] = bit_vec[i];
}
return out_bits;
}
int add_watermark (const std::string& infile, const std::string& outfile, const std::string& bits);
int get_watermark (const std::string& infile, const std::string& orig_pattern);
#endif /* AUDIOWMARK_WM_COMMON_HH */
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment