Commit d1276514 authored by Stefan Westerfeld's avatar Stefan Westerfeld

Merge branch 'limiter'

parents 4a2f0b2d b67de297
Overview of Changes in audiowmark-0.2.1:
* add limiter to avoid clipping during watermark generation
Overview of Changes in audiowmark-0.2.0:
* support input/output streams
......
AC_INIT([audiowmark], [0.2.0])
AC_INIT([audiowmark], [0.2.1])
AC_CONFIG_SRCDIR([src/audiowmark.cc])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
......
......@@ -3,13 +3,14 @@ bin_PROGRAMS = audiowmark
COMMON_SRC = utils.hh utils.cc convcode.hh convcode.cc random.hh random.cc wavdata.cc wavdata.hh \
audiostream.cc audiostream.hh sfinputstream.cc sfinputstream.hh stdoutwavoutputstream.cc stdoutwavoutputstream.hh \
sfoutputstream.cc sfoutputstream.hh rawinputstream.cc rawinputstream.hh rawoutputstream.cc rawoutputstream.hh \
rawconverter.cc rawconverter.hh mp3inputstream.cc mp3inputstream.hh wmcommon.cc wmcommon.hh fft.cc fft.hh
rawconverter.cc rawconverter.hh mp3inputstream.cc mp3inputstream.hh wmcommon.cc wmcommon.hh fft.cc fft.hh \
limiter.cc limiter.hh
COMMON_LIBS = $(SNDFILE_LIBS) $(FFTW_LIBS) $(LIBGCRYPT_LIBS) $(LIBMPG123_LIBS)
audiowmark_SOURCES = audiowmark.cc wmget.cc wmadd.cc $(COMMON_SRC)
audiowmark_LDFLAGS = $(COMMON_LIBS)
noinst_PROGRAMS = testconvcode testrandom testmp3 teststream
noinst_PROGRAMS = testconvcode testrandom testmp3 teststream testlimiter
testconvcode_SOURCES = testconvcode.cc $(COMMON_SRC)
testconvcode_LDFLAGS = $(COMMON_LIBS)
......@@ -22,3 +23,6 @@ testmp3_LDFLAGS = $(COMMON_LIBS)
teststream_SOURCES = teststream.cc $(COMMON_SRC)
teststream_LDFLAGS = $(COMMON_LIBS)
testlimiter_SOURCES = testlimiter.cc $(COMMON_SRC)
testlimiter_LDFLAGS = $(COMMON_LIBS)
possible improvements:
- dynamic bit strength
streaming:
- final documentation
- merge mp3 code
- fix virtual constructor FIXMEs
......@@ -192,6 +192,10 @@ parse_options (int *argc_p,
{
Params::test_no_sync = true;
}
else if (check_arg (argc, argv, &i, "--test-no-limiter"))
{
Params::test_no_limiter = true;
}
else if (check_arg (argc, argv, &i, "--test-truncate", &opt_arg))
{
Params::test_truncate = atoi (opt_arg);
......
#include "limiter.hh"
#include <assert.h>
#include <math.h>
#include <stdio.h>
using std::vector;
using std::max;
Limiter::Limiter (int n_channels, int sample_rate) :
n_channels (n_channels),
sample_rate (sample_rate)
{
}
void
Limiter::set_block_size_ms (int ms)
{
block_size = sample_rate * ms / 1000;
}
void
Limiter::set_ceiling (float new_ceiling)
{
ceiling = new_ceiling;
}
vector<float>
Limiter::process (const vector<float>& samples)
{
assert (block_size >= 1);
assert (samples.size() % n_channels == 0); // process should be called with whole frames
buffer.insert (buffer.end(), samples.begin(), samples.end());
/* need at least two complete blocks in buffer to produce output */
const uint buffered_blocks = buffer.size() / n_channels / block_size;
if (buffered_blocks < 2)
return {};
const uint blocks_todo = buffered_blocks - 1;
vector<float> out (blocks_todo * block_size * n_channels);
for (uint b = 0; b < blocks_todo; b++)
process_block (&buffer[b * block_size * n_channels], &out[b * block_size * n_channels]);
buffer.erase (buffer.begin(), buffer.begin() + blocks_todo * block_size * n_channels);
return out;
}
float
Limiter::block_max (const float *in)
{
float maximum = ceiling;
for (uint x = 0; x < block_size * n_channels; x++)
maximum = max (maximum, fabs (in[x]));
return maximum;
}
void
Limiter::process_block (const float *in, float *out)
{
if (block_max_last < ceiling)
block_max_last = ceiling;
if (block_max_current < ceiling)
block_max_current = block_max (in);
if (block_max_next < ceiling)
block_max_next = block_max (in + block_size * n_channels);
const float scale_start = ceiling / max (block_max_last, block_max_current);
const float scale_end = ceiling / max (block_max_current, block_max_next);
const float scale_step = (scale_end - scale_start) / block_size;
for (size_t i = 0; i < block_size; i++)
{
const float scale = scale_start + i * scale_step;
// debug_scale (scale);
for (uint c = 0; c < n_channels; c++)
out[i * n_channels + c] = in[i * n_channels + c] * scale;
}
block_max_last = block_max_current;
block_max_current = block_max_next;
block_max_next = 0;
}
void
Limiter::debug_scale (float scale)
{
static int debug_scale_samples = 0;
if (debug_scale_samples % (sample_rate / 1000) == 0)
printf ("%f %f\n", double (debug_scale_samples) / sample_rate, scale);
debug_scale_samples++;
}
vector<float>
Limiter::flush()
{
vector<float> out;
vector<float> zblock (1024 * n_channels);
size_t todo = buffer.size();
while (todo > 0)
{
vector<float> block = process (zblock);
if (block.size() > todo)
block.resize (todo);
out.insert (out.end(), block.begin(), block.end());
todo -= block.size();
}
return out;
}
#ifndef AUDIOWMARK_LIMITER_HH
#define AUDIOWMARK_LIMITER_HH
#include <vector>
#include <sys/types.h>
class Limiter
{
float ceiling = 1;
float block_max_last = 0;
float block_max_current = 0;
float block_max_next = 0;
uint block_size = 0;
uint n_channels = 0;
uint sample_rate = 0;
std::vector<float> buffer;
void process_block (const float *in, float *out);
float block_max (const float *in);
void debug_scale (float scale);
public:
Limiter (int n_channels, int sample_rate);
void set_block_size_ms (int value_ms);
void set_ceiling (float ceiling);
std::vector<float> process (const std::vector<float>& samples);
std::vector<float> flush();
};
#endif /* AUDIOWMARK_LIMITER_HH */
#include <string>
#include <vector>
#include <sndfile.h>
#include <assert.h>
#include <math.h>
#include <string.h>
#include <sys/time.h>
#include "sfinputstream.hh"
#include "sfoutputstream.hh"
#include "utils.hh"
#include "limiter.hh"
using std::string;
using std::vector;
using std::max;
using std::min;
static double
gettime()
{
timeval tv;
gettimeofday (&tv, 0);
return tv.tv_sec + tv.tv_usec / 1000000.0;
}
int
perf()
{
Limiter limiter (2, 44100);
limiter.set_block_size_ms (1000);
vector<float> samples (2 * 1024);
int n_frames = 0;
double start = gettime();
for (int i = 0; i < 100000; i++)
{
n_frames += samples.size() / 2;
vector<float> out_samples = limiter.process (samples);
}
double end = gettime();
printf ("%f ns/frame\n", (end - start) * 1000 * 1000 * 1000 / n_frames);
return 0;
}
int
impulses()
{
Limiter limiter (2, 44100);
limiter.set_block_size_ms (3);
limiter.set_ceiling (0.9);
vector<float> in_all, out_all;
int pos = 0;
for (int block = 0; block < 10; block++)
{
vector<float> in_samples;
for (int i = 0; i < 1024; i++)
{
double d = (pos++ % 441) == 440 ? 1.0 : 0.5;
in_samples.push_back (d);
in_samples.push_back (d); /* stereo */
}
vector<float> out_samples = limiter.process (in_samples);
in_all.insert (in_all.end(), in_samples.begin(), in_samples.end());
out_all.insert (out_all.end(), out_samples.begin(), out_samples.end());
}
vector<float> out_samples = limiter.flush();
out_all.insert (out_all.end(), out_samples.begin(), out_samples.end());
assert (in_all.size() == out_all.size());
for (size_t i = 0; i < out_all.size(); i += 2)
{
assert (out_all[i] == out_all[i + 1]); /* stereo */
printf ("%f %f\n", in_all[i], out_all[i]);
}
return 0;
}
int
main (int argc, char **argv)
{
if (argc == 2 && strcmp (argv[1], "perf") == 0)
return perf();
if (argc == 2 && strcmp (argv[1], "impulses") == 0)
return impulses();
SFInputStream in;
SFOutputStream out;
Error err = in.open (argv[1]);
if (err)
{
fprintf (stderr, "testlimiter: open input failed: %s\n", err.message());
return 1;
}
err = out.open (argv[2], in.n_channels(), in.sample_rate(), 16, in.n_frames());
if (err)
{
fprintf (stderr, "testlimiter: open output failed: %s\n", err.message());
return 1;
}
Limiter limiter (in.n_channels(), in.sample_rate());
limiter.set_block_size_ms (1000);
limiter.set_ceiling (0.9);
vector<float> in_samples;
do
{
in.read_frames (in_samples, 1024);
for (auto& s: in_samples)
s *= 1.1;
vector<float> out_samples = limiter.process (in_samples);
out.write_frames (out_samples);
}
while (in_samples.size());
out.write_frames (limiter.flush());
}
......@@ -6,6 +6,7 @@
#include "wmcommon.hh"
#include "fft.hh"
#include "convcode.hh"
#include "limiter.hh"
#include "sfinputstream.hh"
#include "sfoutputstream.hh"
#include "mp3inputstream.hh"
......@@ -643,6 +644,10 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
if (!wm_resampler.init_ok())
return 1;
Limiter limiter (n_channels, in_stream->sample_rate());
limiter.set_block_size_ms (Params::limiter_block_size_ms);
limiter.set_ceiling (Params::limiter_ceiling);
/* for signal to noise ratio */
double snr_delta_power = 0;
double snr_signal_power = 0;
......@@ -687,6 +692,9 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
for (size_t i = 0; i < samples.size(); i++)
samples[i] += orig_samples[i];
if (!Params::test_no_limiter)
samples = limiter.process (samples);
size_t max_write_frames = total_input_frames - total_output_frames;
if (samples.size() > max_write_frames * n_channels)
samples.resize (max_write_frames * n_channels);
......
......@@ -10,6 +10,7 @@ bool Params::snr = false; // compute/show snr while adding waterma
int Params::have_key = 0;
int Params::test_cut = 0; // for sync test
bool Params::test_no_sync = false; // disable sync
bool Params::test_no_limiter = false; // disable limiter
int Params::test_truncate = 0;
Format Params::input_format = Format::AUTO;
......
......@@ -37,8 +37,12 @@ public:
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 constexpr double limiter_block_size_ms = 1000;
static constexpr double limiter_ceiling = 0.99;
static int test_cut; // for sync test
static bool test_no_sync;
static bool test_no_limiter;
static int test_truncate;
static Format input_format;
......
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