Commit b95f6f1d authored by Stefan Westerfeld's avatar Stefan Westerfeld

Merge branch 'a-b-error-correction'

parents 1e1a673c 10f02230
...@@ -2,10 +2,9 @@ ...@@ -2,10 +2,9 @@
== Description == Description
`audiowmark` is an Open Source solution for audio watermarking. A sound file `audiowmark` is an Open Source solution for audio watermarking. A sound file is
(typically wav) is read by the software, and a 128-bit message is stored in a read by the software, and a 128-bit message is stored in a watermark in the
watermark in the output sound file. For human listeners, the files typically output sound file. For human listeners, the files typically sound the same.
sound the same.
However, the 128-bit message can be retrieved from the output sound file. Our However, the 128-bit message can be retrieved from the output sound file. Our
tests show, that even if the file is converted to mp3 or ogg (with bitrate 128 tests show, that even if the file is converted to mp3 or ogg (with bitrate 128
...@@ -22,17 +21,94 @@ later. The algorithm used here is inspired by ...@@ -22,17 +21,94 @@ later. The algorithm used here is inspired by
Martin Steinebach: Digitale Wasserzeichen für Audiodaten. Martin Steinebach: Digitale Wasserzeichen für Audiodaten.
Darmstadt University of Technology 2004, ISBN 3-8322-2507-2 Darmstadt University of Technology 2004, ISBN 3-8322-2507-2
== Adding/Retrieving a Watermark == Adding a Watermark
To add a watermark to the soundfile in.wav with a 128-bit message (which is To add a watermark to the soundfile in.wav with a 128-bit message (which is
specified as hex-string): specified as hex-string):
audiowmark add in.wav out.wav 0123456789abcdef0011223344556677 [subs=+quotes]
....
*$ audiowmark add in.wav out.wav 0123456789abcdef0011223344556677*
Input: in.wav
Output: out.wav
Message: 0123456789abcdef0011223344556677
Strength: 10
To get the 128-bit message from the watermarked file, use: Time: 3:59
Sample Rate: 48000
Channels: 2
Data Blocks: 4
Volume Norm: 0.987 (-0.12 dB)
....
The most important options for adding a watermark are:
--key <filename>::
Use watermarking key from file <filename> (see <<key>>).
audiowmark get out.wav --strength <s>::
Set the watermarking strength (see <<strength>>).
== Retrieving a Watermark
To get the 128-bit message from the watermarked file, use:
[subs=+quotes]
....
*$ audiowmark get out.wav*
pattern 0:05 0123456789abcdef0011223344556677 1.324 0.059 A
pattern 0:57 0123456789abcdef0011223344556677 1.413 0.112 B
pattern 0:57 0123456789abcdef0011223344556677 1.368 0.086 AB
pattern 1:49 0123456789abcdef0011223344556677 1.302 0.098 A
pattern 2:40 0123456789abcdef0011223344556677 1.361 0.093 B
pattern 2:40 0123456789abcdef0011223344556677 1.331 0.096 AB
pattern all 0123456789abcdef0011223344556677 1.350 0.054
....
The output of `audiowmark get` is designed to be machine readable. Each line
that starts with `pattern` contains one decoded message. The fields are
seperated by one or more space characters. The first field is a *timestamp*
indicating the position of the data block. The second field is the *decoded
message*. For most purposes this is all you need to know.
The software was designed under the assumption that you - the user - will be
able to decide whether a message is correct or not. To do this, on watermarking
song files, you could list each message you embedded in a database. During
retrieval, you should look up each pattern `audiowmark get` outputs in the
database. If the message is not found, then you should assume that a decoding
error occurred. In our example each pattern was decoded correctly, because
the watermark was not damaged at all, but if you for instance use lossy
compression (with a low bitrate), it may happen that only some of the decoded
patterns are correct. Or none, if the watermark was damaged too much.
The third field is the *sync score* (higher is better). The synchronization
algorithm tries to find valid data blocks in the audio file, that become
candidates for decoding.
The fourth field is the *decoding error* (lower is better). During message
decoding, we use convolutional codes for error correction, to make the
watermarking more robust.
The fifth field is the *block type*. There are two types of data blocks,
A blocks and B blocks. A single data block can be decoded alone, as it
contains a complete message. However, if during watermark detection an
A block followed by a B block was found, these two can be decoded
together (then this field will be AB), resulting in even higher error
correction capacity than one block alone would have.
To improve the error correction capacity even further, the `all` pattern
combines all data blocks that are available. The combined decoded
message will often be the most reliable result (meaning that even if all
other patterns were incorrect, this could still be right).
The most important options for getting a watermark are:
--key <filename>::
Use watermarking key from file <filename> (see <<key>>).
--strength <s>::
Set the watermarking strength (see <<strength>>).
[[key]]
== Watermark Key == Watermark Key
Since the software is Open Source, a watermarking key should be used to ensure Since the software is Open Source, a watermarking key should be used to ensure
...@@ -52,6 +128,7 @@ and can be used for the add/get commands as follows: ...@@ -52,6 +128,7 @@ and can be used for the add/get commands as follows:
audiowmark add --key test.key in.wav out.wav 0123456789abcdef0011223344556677 audiowmark add --key test.key in.wav out.wav 0123456789abcdef0011223344556677
audiowmark get --key test.key out.wav audiowmark get --key test.key out.wav
[[strength]]
== Watermark Strength == Watermark Strength
The watermark strength parameter affects how much the watermarking algorithm The watermark strength parameter affects how much the watermarking algorithm
...@@ -74,7 +151,7 @@ watermark. Fractional strengths (like 7.5) are possible. ...@@ -74,7 +151,7 @@ watermark. Fractional strengths (like 7.5) are possible.
== Dependencies == Dependencies
If you compile from source, audiowmark needs the follwing libraries: If you compile from source, audiowmark needs the following libraries:
* libfftw3 * libfftw3
* libsndfile * libsndfile
......
...@@ -42,13 +42,14 @@ namespace Params ...@@ -42,13 +42,14 @@ namespace Params
static int sync_frames_per_bit = 85; static int sync_frames_per_bit = 85;
static int sync_search_step = 256; static int sync_search_step = 256;
static int sync_search_fine = 8; static int sync_search_fine = 8;
static double sync_threshold1 = 0.5; // minimum grid quality value (search_step grid)
static double sync_threshold2 = 0.7; // minimum refined quality static double sync_threshold2 = 0.7; // minimum refined quality
static size_t frames_pad_start = 250; // padding at start, in case track starts with silence static size_t frames_pad_start = 250; // padding at start, in case track starts with silence
static int mark_sample_rate = 44100; // watermark generation and detection sample rate static int mark_sample_rate = 44100; // watermark generation and detection sample rate
static int test_cut = 0; // for sync test static int test_cut = 0; // for sync test
static bool test_no_sync = false; // disable sync
static int test_truncate = 0;
} }
void void
...@@ -60,25 +61,17 @@ print_usage() ...@@ -60,25 +61,17 @@ print_usage()
printf (" * create a watermarked wav file with a message\n"); printf (" * create a watermarked wav file with a message\n");
printf (" audiowmark add <input_wav> <watermarked_wav> <message_hex>\n"); printf (" audiowmark add <input_wav> <watermarked_wav> <message_hex>\n");
printf ("\n"); printf ("\n");
printf (" * blind decoding (retrieve message without original file)\n"); printf (" * retrieve message\n");
printf (" audiowmark get <watermarked_wav>\n"); printf (" audiowmark get <watermarked_wav>\n");
printf ("\n"); printf ("\n");
printf (" * compute bit error rate for blind decoding\n"); printf (" * compare watermark message with expected message\n");
printf (" audiowmark cmp <watermarked_wav> <message_hex>\n"); printf (" audiowmark cmp <watermarked_wav> <message_hex>\n");
printf ("\n"); printf ("\n");
#if 0
printf (" * retrieve message with original file\n");
printf (" audiowmark get-delta <input_wav> <watermarked_wav>\n");
printf ("\n");
printf (" * compute bit error rate for decoding with original file\n");
printf (" audiowmark cmp-delta <input_wav> <watermarked_wav> <message_hex>\n");
printf ("\n");
#endif
printf (" * generate 128-bit watermarking key, to be used with --key option\n"); printf (" * generate 128-bit watermarking key, to be used with --key option\n");
printf (" audiowmark gen-key <key_file>\n"); printf (" audiowmark gen-key <key_file>\n");
printf ("\n"); printf ("\n");
printf ("Global options:\n"); printf ("Global options:\n");
printf (" --strength set watermark strength [%.6g]\n", Params::water_delta * 1000); printf (" --strength <s> set watermark strength [%.6g]\n", Params::water_delta * 1000);
printf (" --linear disable non-linear bit storage\n"); printf (" --linear disable non-linear bit storage\n");
printf (" --key <file> load watermarking key from file\n"); printf (" --key <file> load watermarking key from file\n");
} }
...@@ -187,6 +180,14 @@ parse_options (int *argc_p, ...@@ -187,6 +180,14 @@ parse_options (int *argc_p,
{ {
Params::test_cut = atoi (opt_arg); Params::test_cut = atoi (opt_arg);
} }
else if (check_arg (argc, argv, &i, "--test-no-sync"))
{
Params::test_no_sync = true;
}
else if (check_arg (argc, argv, &i, "--test-truncate", &opt_arg))
{
Params::test_truncate = atoi (opt_arg);
}
} }
/* resort argc/argv */ /* resort argc/argv */
...@@ -279,7 +280,7 @@ randomize_bit_order (const vector<T>& bit_vec, bool encode) ...@@ -279,7 +280,7 @@ randomize_bit_order (const vector<T>& bit_vec, bool encode)
} }
vector<vector<complex<float>>> vector<vector<complex<float>>>
compute_frame_ffts (const WavData& wav_data, size_t start_index, size_t frame_count) compute_frame_ffts (const WavData& wav_data, size_t start_index, size_t frame_count, const vector<int>& want_frames)
{ {
vector<vector<complex<float>>> fft_out; vector<vector<complex<float>>> fft_out;
...@@ -312,26 +313,35 @@ compute_frame_ffts (const WavData& wav_data, size_t start_index, size_t frame_co ...@@ -312,26 +313,35 @@ compute_frame_ffts (const WavData& wav_data, size_t start_index, size_t frame_co
for (size_t f = 0; f < frame_count; f++) for (size_t f = 0; f < frame_count; f++)
{ {
for (int ch = 0; ch < wav_data.n_channels(); ch++) if (!want_frames.empty() && !want_frames[f])
{
/* skip fft calculation completely if frame is not in want_frames */
for (int ch = 0; ch < wav_data.n_channels(); ch++)
fft_out.emplace_back();
}
else
{ {
const auto& samples = wav_data.samples(); for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
const auto& samples = wav_data.samples();
size_t pos = (start_index + f * Params::frame_size) * wav_data.n_channels() + ch; size_t pos = (start_index + f * Params::frame_size) * wav_data.n_channels() + ch;
assert (pos + (Params::frame_size - 1) * wav_data.n_channels() < samples.size()); assert (pos + (Params::frame_size - 1) * wav_data.n_channels() < samples.size());
/* deinterleave frame data and apply window */ /* deinterleave frame data and apply window */
for (size_t x = 0; x < Params::frame_size; x++) for (size_t x = 0; x < Params::frame_size; x++)
{ {
frame[x] = samples[pos] * window[x]; frame[x] = samples[pos] * window[x];
pos += wav_data.n_channels(); pos += wav_data.n_channels();
} }
/* FFT transform */ /* FFT transform */
fftar_float (Params::frame_size, frame, frame_fft); fftar_float (Params::frame_size, frame, frame_fft);
/* complex<float> and frame_fft have the same layout in memory */ /* complex<float> and frame_fft have the same layout in memory */
const complex<float> *first = (complex<float> *) frame_fft; const complex<float> *first = (complex<float> *) frame_fft;
const complex<float> *last = first + Params::frame_size / 2 + 1; const complex<float> *last = first + Params::frame_size / 2 + 1;
fft_out.emplace_back (first, last); fft_out.emplace_back (first, last);
}
} }
} }
free_array_float (frame); free_array_float (frame);
...@@ -339,6 +349,49 @@ compute_frame_ffts (const WavData& wav_data, size_t start_index, size_t frame_co ...@@ -339,6 +349,49 @@ compute_frame_ffts (const WavData& wav_data, size_t start_index, size_t frame_co
return fft_out; return fft_out;
} }
size_t mark_data_frame_count();
size_t mark_sync_frame_count();
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);
}
void void
mark_bit_linear (int f, const vector<complex<float>>& fft_out, vector<complex<float>>& fft_delta_spect, int data_bit, Random::Stream random_stream) mark_bit_linear (int f, const vector<complex<float>>& fft_out, vector<complex<float>>& fft_delta_spect, int data_bit, Random::Stream random_stream)
{ {
...@@ -382,7 +435,7 @@ mark_bit_linear (int f, const vector<complex<float>>& fft_out, vector<complex<fl ...@@ -382,7 +435,7 @@ mark_bit_linear (int f, const vector<complex<float>>& fft_out, vector<complex<fl
size_t size_t
mark_data_frame_count() mark_data_frame_count()
{ {
return conv_code_size (Params::payload_size) * Params::frames_per_bit; return conv_code_size (ConvBlockType::a, Params::payload_size) * Params::frames_per_bit;
} }
struct MixEntry struct MixEntry
...@@ -405,7 +458,7 @@ gen_mix_entries() ...@@ -405,7 +458,7 @@ gen_mix_entries()
assert (up.size() == down.size()); assert (up.size() == down.size());
for (size_t i = 0; i < up.size(); i++) for (size_t i = 0; i < up.size(); i++)
mix_entries.push_back ({ f, up[i], down[i] }); mix_entries.push_back ({ data_frame_pos (f), up[i], down[i] });
} }
Random random (/* seed */ 0, Random::Stream::mix); Random random (/* seed */ 0, Random::Stream::mix);
random.shuffle (mix_entries); random.shuffle (mix_entries);
...@@ -469,7 +522,7 @@ mark_data (const WavData& wav_data, int start_frame, const vector<vector<complex ...@@ -469,7 +522,7 @@ mark_data (const WavData& wav_data, int start_frame, const vector<vector<complex
{ {
for (int ch = 0; ch < wav_data.n_channels(); ch++) for (int ch = 0; ch < wav_data.n_channels(); ch++)
{ {
size_t index = (start_frame + f) * wav_data.n_channels() + ch; size_t index = (start_frame + data_frame_pos (f)) * wav_data.n_channels() + ch;
mark_bit_linear (f, fft_out[index], fft_delta_spect[index], bitvec[f / Params::frames_per_bit], Random::Stream::data_up_down); mark_bit_linear (f, fft_out[index], fft_delta_spect[index], bitvec[f / Params::frames_per_bit], Random::Stream::data_up_down);
} }
...@@ -484,7 +537,7 @@ mark_sync_frame_count() ...@@ -484,7 +537,7 @@ mark_sync_frame_count()
} }
void void
mark_sync (const WavData& wav_data, int start_frame, const vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_delta_spect) mark_sync (const WavData& wav_data, int start_frame, const vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_delta_spect, int ab)
{ {
assert (fft_out.size() >= (start_frame + mark_sync_frame_count()) * wav_data.n_channels()); assert (fft_out.size() >= (start_frame + mark_sync_frame_count()) * wav_data.n_channels());
...@@ -495,8 +548,8 @@ mark_sync (const WavData& wav_data, int start_frame, const vector<vector<complex ...@@ -495,8 +548,8 @@ mark_sync (const WavData& wav_data, int start_frame, const vector<vector<complex
{ {
for (int ch = 0; ch < wav_data.n_channels(); ch++) for (int ch = 0; ch < wav_data.n_channels(); ch++)
{ {
size_t index = (start_frame + f) * wav_data.n_channels() + ch; size_t index = (start_frame + sync_frame_pos (f)) * wav_data.n_channels() + ch;
int data_bit = (f / Params::sync_frames_per_bit) & 1; /* write 010101 */ int data_bit = (f / Params::sync_frames_per_bit + ab) & 1; /* write 010101 for a block, 101010 for b block */
mark_bit_linear (f, fft_out[index], fft_delta_spect[index], data_bit, Random::Stream::sync_up_down); mark_bit_linear (f, fft_out[index], fft_delta_spect[index], data_bit, Random::Stream::sync_up_down);
} }
...@@ -608,10 +661,8 @@ add_watermark (const string& infile, const string& outfile, const string& bits) ...@@ -608,10 +661,8 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
printf ("Strength: %.6g\n\n", Params::water_delta * 1000); printf ("Strength: %.6g\n\n", Params::water_delta * 1000);
/* add forward error correction, bitvec will now be a lot larger */ /* add forward error correction, bitvec will now be a lot larger */
bitvec = randomize_bit_order (conv_encode (bitvec), /* encode */ true); auto bitvec_a = randomize_bit_order (conv_encode (ConvBlockType::a, bitvec), /* encode */ true);
auto bitvec_b = randomize_bit_order (conv_encode (ConvBlockType::b, bitvec), /* encode */ true);
/* pad with zeros to match block_size */
bitvec.resize (mark_data_frame_count() / Params::frames_per_bit);
WavData orig_wav_data; WavData orig_wav_data;
if (!orig_wav_data.load (infile)) if (!orig_wav_data.load (infile))
...@@ -647,7 +698,7 @@ add_watermark (const string& infile, const string& outfile, const string& bits) ...@@ -647,7 +698,7 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
/* we have extra space for the padded wave data -> truncated before save */ /* we have extra space for the padded wave data -> truncated before save */
vector<float> out_signal (wav_data.n_values()); vector<float> out_signal (wav_data.n_values());
vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data, 0, frame_count (wav_data)); vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data, 0, frame_count (wav_data), /* want all frames */ {});
vector<vector<complex<float>>> fft_delta_spect; vector<vector<complex<float>>> fft_delta_spect;
for (int f = 0; f < frame_count (wav_data); f++) for (int f = 0; f < frame_count (wav_data); f++)
{ {
...@@ -667,13 +718,12 @@ add_watermark (const string& infile, const string& outfile, const string& bits) ...@@ -667,13 +718,12 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
/* embed sync|data|sync|data|... */ /* embed sync|data|sync|data|... */
while (frame_index + (mark_sync_frame_count() + mark_data_frame_count()) < size_t (frame_count (wav_data))) while (frame_index + (mark_sync_frame_count() + mark_data_frame_count()) < size_t (frame_count (wav_data)))
{ {
mark_sync (wav_data, frame_index, fft_out, fft_delta_spect); mark_sync (wav_data, frame_index, fft_out, fft_delta_spect, (data_blocks & 1));
frame_index += mark_sync_frame_count(); mark_data (wav_data, frame_index, fft_out, fft_delta_spect, (data_blocks & 1) ? bitvec_b : bitvec_a);
data_blocks++; frame_index += mark_sync_frame_count() + mark_data_frame_count();
mark_data (wav_data, frame_index, fft_out, fft_delta_spect, bitvec); data_blocks++;
frame_index += mark_data_frame_count();
} }
/* padding at end */ /* padding at end */
while (frame_index < size_t (frame_count (wav_data))) while (frame_index < size_t (frame_count (wav_data)))
...@@ -823,11 +873,11 @@ normalize_soft_bits (const vector<float>& soft_bits) ...@@ -823,11 +873,11 @@ normalize_soft_bits (const vector<float>& soft_bits)
} }
vector<float> vector<float>
mix_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_orig_out, int n_channels) mix_decode (vector<vector<complex<float>>>& fft_out, int n_channels)
{ {
vector<float> raw_bit_vec; vector<float> raw_bit_vec;
const int frame_count = fft_out.size() / n_channels; const int frame_count = mark_data_frame_count();
vector<MixEntry> mix_entries = gen_mix_entries(); vector<MixEntry> mix_entries = gen_mix_entries();
...@@ -847,12 +897,6 @@ mix_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float ...@@ -847,12 +897,6 @@ mix_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float
umag += db_from_factor (abs (fft_out[index][u]), min_db); umag += db_from_factor (abs (fft_out[index][u]), min_db);
dmag += db_from_factor (abs (fft_out[index][d]), min_db); dmag += db_from_factor (abs (fft_out[index][d]), min_db);
if (index < fft_orig_out.size()) /* non-blind decode? */
{
umag -= db_from_factor (abs (fft_orig_out[index][u]), min_db);
dmag -= db_from_factor (abs (fft_orig_out[index][d]), min_db);
}
} }
} }
if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1)) if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1))
...@@ -866,36 +910,28 @@ mix_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float ...@@ -866,36 +910,28 @@ mix_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float
} }
vector<float> vector<float>
linear_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_orig_out, int n_channels) linear_decode (vector<vector<complex<float>>>& fft_out, int n_channels)
{ {
vector<float> raw_bit_vec; vector<float> raw_bit_vec;
const int frame_count = mark_data_frame_count();
double umag = 0, dmag = 0; double umag = 0, dmag = 0;
const int frame_count = fft_out.size() / n_channels;
for (int f = 0; f < frame_count; f++) for (int f = 0; f < frame_count; f++)
{ {
for (int ch = 0; ch < n_channels; ch++) for (int ch = 0; ch < n_channels; ch++)
{ {
const size_t index = f * n_channels + ch; const size_t index = data_frame_pos (f) * n_channels + ch;
vector<int> up; vector<int> up;
vector<int> down; vector<int> down;
get_up_down (f, up, down, Random::Stream::data_up_down); get_up_down (f, up, down, Random::Stream::data_up_down);
const double min_db = -96; const double min_db = -96;
for (auto u : up) for (auto u : up)
{ umag += db_from_factor (abs (fft_out[index][u]), min_db);
umag += db_from_factor (abs (fft_out[index][u]), min_db);
if (index < fft_orig_out.size())
umag -= db_from_factor (abs (fft_orig_out[index][u]), min_db);
}
for (auto d : down) for (auto d : down)
{ dmag += db_from_factor (abs (fft_out[index][d]), min_db);
dmag += db_from_factor (abs (fft_out[index][d]), min_db);
if (index < fft_orig_out.size())
dmag -= db_from_factor (abs (fft_orig_out[index][d]), min_db);
}
} }
if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1)) if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1))
{ {
...@@ -943,17 +979,17 @@ class SyncFinder ...@@ -943,17 +979,17 @@ class SyncFinder
get_up_down (f + bit * Params::sync_frames_per_bit, frame_up, frame_down, Random::Stream::sync_up_down); get_up_down (f + bit * Params::sync_frames_per_bit, frame_up, frame_down, Random::Stream::sync_up_down);
for (auto u : frame_up) for (auto u : frame_up)
up[bit].push_back (u - Params::min_band + f * n_bands * wav_data.n_channels()); up[bit].push_back (u - Params::min_band + sync_frame_pos (f + bit * Params::sync_frames_per_bit) * n_bands * wav_data.n_channels());
for (auto d : frame_down) for (auto d : frame_down)
down[bit].push_back (d - Params::min_band + f * n_bands * wav_data.n_channels()); down[bit].push_back (d - Params::min_band + sync_frame_pos (f + bit * Params::sync_frames_per_bit) * n_bands * wav_data.n_channels());
} }
sort (up[bit].begin(), up[bit].end()); sort (up[bit].begin(), up[bit].end());
sort (down[bit].begin(), down[bit].end()); sort (down[bit].begin(), down[bit].end());
} }
} }
double double
sync_decode (const WavData& wav_data, const size_t start_frame, const vector<float>& fft_out_db) sync_decode (const WavData& wav_data, const size_t start_frame, const vector<float>& fft_out_db, ConvBlockType *block_type)
{ {
double sync_quality = 0; double sync_quality = 0;
...@@ -964,7 +1000,7 @@ class SyncFinder ...@@ -964,7 +1000,7 @@ class SyncFinder
for (int ch = 0; ch < wav_data.n_channels(); ch++) for (int ch = 0; ch < wav_data.n_channels(); ch++)
{ {
const int index = (((bit * Params::sync_frames_per_bit) + start_frame) * wav_data.n_channels() + ch) * n_bands; const int index = (start_frame * wav_data.n_channels() + ch) * n_bands;
for (size_t i = 0; i < up[bit].size(); i++) for (size_t i = 0; i < up[bit].size(); i++)
{ {
...@@ -989,12 +1025,23 @@ class SyncFinder ...@@ -989,12 +1025,23 @@ class SyncFinder
} }
sync_quality /= Params::sync_bits; sync_quality /= Params::sync_bits;
sync_quality = normalize_sync_quality (sync_quality); sync_quality = normalize_sync_quality (sync_quality);
return sync_quality;
if (sync_quality < 0)
{
*block_type = ConvBlockType::b;
return -sync_quality;
}
else
{
*block_type = ConvBlockType::a;
return sync_quality;
}
} }
public: public:
struct Score { struct Score {
size_t index; size_t index;
double quality; double quality;
ConvBlockType block_type;
}; };
vector<Score> vector<Score>
search (const WavData& wav_data) search (const WavData& wav_data)
...@@ -1002,6 +1049,18 @@ public: ...@@ -1002,6 +1049,18 @@ public:
vector<Score> result_scores; vector<Score> result_scores;
vector<Score> sync_scores; vector<Score> sync_scores;
if (Params::test_no_sync)
{
const size_t expect0 = Params::frames_pad_start * Params::frame_size;
const size_t expect_step = (mark_sync_frame_count() + mark_data_frame_count()) * Params::frame_size;
const size_t expect_end = frame_count (wav_data) * Params::frame_size;
int ab = 0;
for (size_t expect_index = expect0; expect_index + expect_step < expect_end; expect_index += expect_step)
result_scores.push_back (Score { expect_index, 1.0, (ab++ & 1) ? ConvBlockType::b : ConvBlockType::a });
return result_scores;
}
init_up_down (wav_data); init_up_down (wav_data);
vector<float> fft_db; vector<float> fft_db;
...@@ -1010,23 +1069,38 @@ public: ...@@ -1010,23 +1069,38 @@ public:
size_t n_bands = Params::max_band - Params::min_band + 1; size_t n_bands = Params::max_band - Params::min_band + 1;
for (size_t sync_shift = 0; sync_shift < Params::frame_size; sync_shift += Params::sync_search_step) for (size_t sync_shift = 0; sync_shift < Params::frame_size; sync_shift += Params::sync_search_step)
{ {
sync_fft (wav_data, sync_shift, frame_count (wav_data) - 1, fft_db); sync_fft (wav_data, sync_shift, frame_count (wav_data) - 1, fft_db, /* want all frames */ {});
for (int start_frame = 0; start_frame < frame_count (wav_data); start_frame++) for (int start_frame = 0; start_frame < frame_count (wav_data); start_frame++)
{ {
const size_t sync_index = start_frame * Params::frame_size + sync_shift; const size_t sync_index = start_frame * Params::frame_size + sync_shift;
if ((start_frame + mark_sync_frame_count()) * wav_data.n_channels() * n_bands < fft_db.size()) if ((start_frame + mark_sync_frame_count() + mark_data_frame_count()) * wav_data.n_channels() * n_bands < fft_db.size())
{ {
double quality = sync_decode (wav_data, start_frame, fft_db); ConvBlockType block_type;
double quality = sync_decode (wav_data, start_frame, fft_db, &block_type);
// printf ("%zd %f\n", sync_index, quality); // printf ("%zd %f\n", sync_index, quality);
sync_scores.emplace_back (Score { sync_index, quality }); sync_scores.emplace_back (Score { sync_index, quality, block_type });
} }
} }
} }
sort (sync_scores.begin(), sync_scores.end(), [] (const Score& a, const Score &b) { return a.index < b.index; }); sort (sync_scores.begin(), sync_scores.end(), [] (const Score& a, const Score &b) { return a.index < b.index; });
vector<int> want_frames (mark_sync_frame_count() + mark_data_frame_count());
for (size_t f = 0; f < mark_sync_frame_count(); f++)
want_frames[sync_frame_pos (f)] = 1;
/* for strength 8 and above:
* -> more false positive candidates are rejected, so we can use a lower threshold
*
* for strength 7 and below:
* -> we need a higher threshold, because otherwise watermark detection takes too long
*/
const double strength = Params::water_delta * 1000;
const double sync_threshold1 = strength > 7.5 ? 0.4 : 0.5;
for (size_t i = 0; i < sync_scores.size(); i++) for (size_t i = 0; i < sync_scores.size(); i++)
{ {
// printf ("%zd %f\n", sync_scores[i].index, sync_scores[i].quality); // printf ("%zd %f\n", sync_scores[i].index, sync_scores[i].quality);
if (sync_scores[i].quality > Params::sync_threshold1) if (sync_scores[i].quality > sync_threshold1)
{ {
double q_last = -1; double q_last = -1;
double q_next = -1; double q_next = -1;
...@@ -1042,17 +1116,19 @@ public: ...@@ -1042,17 +1116,19 @@ public:
//printf ("%zd %s %f", sync_scores[i].index, find_closest_sync (sync_scores[i].index), sync_scores[i].quality); //printf ("%zd %s %f", sync_scores[i].index, find_closest_sync (sync_scores[i].index), sync_scores[i].quality);
// refine match // refine match
double best_quality = sync_scores[i].quality; double best_quality = sync_scores[i].quality;
size_t best_index = sync_scores[i].index; size_t best_index = sync_scores[i].index;
ConvBlockType best_block_type = sync_scores[i].block_type; /* doesn't really change during refinement */
int start = std::max (int (sync_scores[i].index) - Params::sync_search_step, 0); int start = std::max (int (sync_scores[i].index) - Params::sync_search_step, 0);
int end = sync_scores[i].index + Params::sync_search_step; int end = sync_scores[i].index + Params::sync_search_step;
for (int fine_index = start; fine_index <= end; fine_index += Params::sync_search_fine) for (int fine_index = start; fine_index <= end; fine_index += Params::sync_search_fine)
{ {
sync_fft (wav_data, fine_index, mark_sync_frame_count(), fft_db); sync_fft (wav_data, fine_index, mark_sync_frame_count() + mark_data_frame_count(), fft_db, want_frames);
if (fft_db.size()) if (fft_db.size())
{ {
double q = sync_decode (wav_data, 0, fft_db); ConvBlockType block_type;
double q = sync_decode (wav_data, 0, fft_db, &block_type);
if (q > best_quality) if (q > best_quality)
{ {
...@@ -1063,7 +1139,7 @@ public: ...@@ -1063,7 +1139,7 @@ public:
} }
//printf (" => refined: %zd %s %f\n", best_index, find_closest_sync (best_index), best_quality); //printf (" => refined: %zd %s %f\n", best_index, find_closest_sync (best_index), best_quality);
if (best_quality > Params::sync_threshold2) if (best_quality > Params::sync_threshold2)
result_scores.push_back (Score { best_index, best_quality }); result_scores.push_back (Score { best_index, best_quality, best_block_type });
} }
} }
} }
...@@ -1071,17 +1147,26 @@ public: ...@@ -1071,17 +1147,26 @@ public:
} }
private: private:
void void
sync_fft (const WavData& wav_data, size_t index, size_t count, vector<float>& fft_out_db) sync_fft (const WavData& wav_data, size_t index, size_t count, vector<float>& fft_out_db, const vector<int>& want_frames)
{ {
fft_out_db.clear(); fft_out_db.clear();
/* computing db-magnitude is expensive, so we better do it here */ /* computing db-magnitude is expensive, so we better do it here */
for (const vector<complex<float>>& spect : compute_frame_ffts (wav_data, index, count)) vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data, index, count, want_frames);
for (size_t p = 0; p < fft_out.size(); p++)
{ {
const double min_db = -96; const double min_db = -96;
for (int i = Params::min_band; i <= Params::max_band; i++) if (!fft_out[p].size()) // not in want_frames?
fft_out_db.push_back (db_from_factor (abs (spect[i]), min_db)); {
for (int i = Params::min_band; i <= Params::max_band; i++)
fft_out_db.push_back (min_db);
}
else
{
for (int i = Params::min_band; i <= Params::max_band; i++)
fft_out_db.push_back (db_from_factor (abs (fft_out[p][i]), min_db));
}
} }
} }
...@@ -1114,25 +1199,29 @@ decode_and_report (const WavData& wav_data, const string& orig_pattern) ...@@ -1114,25 +1199,29 @@ decode_and_report (const WavData& wav_data, const string& orig_pattern)
SyncFinder sync_finder; SyncFinder sync_finder;
vector<SyncFinder::Score> sync_scores = sync_finder.search (wav_data); vector<SyncFinder::Score> sync_scores = sync_finder.search (wav_data);
auto decode_single = [&] (const vector<float>& raw_bit_vec, SyncFinder::Score sync_score) auto report_pattern = [&] (SyncFinder::Score sync_score, const vector<int>& bit_vec, float decode_error)
{ {
assert (raw_bit_vec.size() == conv_code_size (Params::payload_size));
vector<float> soft_bit_vec = normalize_soft_bits (raw_bit_vec);
float decode_error = 0;
vector<int> bit_vec = conv_decode_soft (randomize_bit_order (soft_bit_vec, /* encode */ false), &decode_error);
if (sync_score.index) if (sync_score.index)
{ {
const char *block_str = nullptr;
switch (sync_score.block_type)
{
case ConvBlockType::a: block_str = "A";
break;
case ConvBlockType::b: block_str = "B";
break;
case ConvBlockType::ab: block_str = "AB";
break;
}
const int seconds = sync_score.index / wav_data.sample_rate(); const int seconds = sync_score.index / wav_data.sample_rate();
printf ("pattern %2d:%02d %s %.3f %.3f\n", seconds / 60, seconds % 60, bit_vec_to_str (bit_vec).c_str(), sync_score.quality, decode_error); printf ("pattern %2d:%02d %s %.3f %.3f %s\n", seconds / 60, seconds % 60, bit_vec_to_str (bit_vec).c_str(),
sync_score.quality, decode_error, block_str);
} }
else /* this is the combined pattern "all" */ else /* this is the combined pattern "all" */
{ {
printf ("pattern all %s %.3f %.3f\n", bit_vec_to_str (bit_vec).c_str(), sync_score.quality, decode_error); printf ("pattern all %s %.3f %.3f\n", bit_vec_to_str (bit_vec).c_str(), sync_score.quality, decode_error);
} }
if (!orig_pattern.empty()) if (!orig_pattern.empty())
{ {
bool match = true; bool match = true;
...@@ -1148,39 +1237,88 @@ decode_and_report (const WavData& wav_data, const string& orig_pattern) ...@@ -1148,39 +1237,88 @@ decode_and_report (const WavData& wav_data, const string& orig_pattern)
total_count++; total_count++;
}; };
vector<float> raw_bit_vec_all (conv_code_size (Params::payload_size)); vector<float> raw_bit_vec_all (conv_code_size (ConvBlockType::ab, Params::payload_size));
vector<int> raw_bit_vec_norm (2);
SyncFinder::Score score_all { 0, 0 }; SyncFinder::Score score_all { 0, 0 };
SyncFinder::Score score_ab { 0, 0, ConvBlockType::ab };
ConvBlockType last_block_type = ConvBlockType::b;
vector<vector<float>> ab_raw_bit_vec (2);
vector<float> ab_quality (2);
for (auto sync_score : sync_scores) for (auto sync_score : sync_scores)
{ {
const size_t count = mark_data_frame_count(); const size_t count = mark_sync_frame_count() + mark_data_frame_count();
const size_t index = sync_score.index + (mark_sync_frame_count() * Params::frame_size); const size_t index = sync_score.index;
const int ab = (sync_score.block_type == ConvBlockType::b); /* A -> 0, B -> 1 */
auto fft_range_out = compute_frame_ffts (wav_data, index, count); auto fft_range_out = compute_frame_ffts (wav_data, index, count, /* want all frames */ {});
if (fft_range_out.size()) if (fft_range_out.size())
{ {
vector<vector<complex<float>>> junk; /* ---- retrieve bits from watermark ---- */
vector<float> raw_bit_vec; vector<float> raw_bit_vec;
if (Params::mix) if (Params::mix)
{ {
raw_bit_vec = mix_decode (fft_range_out, junk, wav_data.n_channels()); raw_bit_vec = mix_decode (fft_range_out, wav_data.n_channels());
} }
else else
{ {
raw_bit_vec = linear_decode (fft_range_out, junk, wav_data.n_channels()); raw_bit_vec = linear_decode (fft_range_out, wav_data.n_channels());
} }
decode_single (raw_bit_vec, sync_score); assert (raw_bit_vec.size() == conv_code_size (ConvBlockType::a, Params::payload_size));
raw_bit_vec = randomize_bit_order (raw_bit_vec, /* encode */ false);
/* ---- deal with this pattern ---- */
float decode_error = 0;
vector<int> bit_vec = conv_decode_soft (sync_score.block_type, normalize_soft_bits (raw_bit_vec), &decode_error);
report_pattern (sync_score, bit_vec, decode_error);
/* ---- update "all" pattern ---- */
score_all.quality += sync_score.quality; score_all.quality += sync_score.quality;
for (size_t i = 0; i < raw_bit_vec_all.size(); i++)
raw_bit_vec_all[i] += raw_bit_vec[i]; for (size_t i = 0; i < raw_bit_vec.size(); i++)
{
raw_bit_vec_all[i * 2 + ab] += raw_bit_vec[i];
}
raw_bit_vec_norm[ab]++;
/* ---- if last block was A & this block is B => deal with combined AB block */
ab_raw_bit_vec[ab] = raw_bit_vec;
ab_quality[ab] = sync_score.quality;
if (last_block_type == ConvBlockType::a && sync_score.block_type == ConvBlockType::b)
{
/* join A and B block -> AB block */
vector<float> ab_bits (raw_bit_vec.size() * 2);
for (size_t i = 0; i < raw_bit_vec.size(); i++)
{
ab_bits[i * 2] = ab_raw_bit_vec[0][i];
ab_bits[i * 2 + 1] = ab_raw_bit_vec[1][i];
}
vector<int> bit_vec = conv_decode_soft (ConvBlockType::ab, normalize_soft_bits (ab_bits), &decode_error);
score_ab.index = sync_score.index;
score_ab.quality = (ab_quality[0] + ab_quality[1]) / 2;
report_pattern (score_ab, bit_vec, decode_error);
}
last_block_type = sync_score.block_type;
} }
} }
if (total_count > 1) /* all pattern: average soft bits of all watermarks and decode */ if (total_count > 1) /* all pattern: average soft bits of all watermarks and decode */
{ {
score_all.quality /= total_count; for (size_t i = 0; i < raw_bit_vec_all.size(); i += 2)
decode_single (raw_bit_vec_all, score_all); {
raw_bit_vec_all[i] /= max (raw_bit_vec_norm[0], 1); /* normalize A soft bits with number of A blocks */
raw_bit_vec_all[i + 1] /= max (raw_bit_vec_norm[1], 1); /* normalize B soft bits with number of B blocks */
}
score_all.quality /= raw_bit_vec_norm[0] + raw_bit_vec_norm[1];
vector<float> soft_bit_vec = normalize_soft_bits (raw_bit_vec_all);
float decode_error = 0;
vector<int> bit_vec = conv_decode_soft (ConvBlockType::ab, soft_bit_vec, &decode_error);
report_pattern (score_all, bit_vec, decode_error);
} }
if (!orig_pattern.empty()) if (!orig_pattern.empty())
...@@ -1218,6 +1356,17 @@ get_watermark (const string& infile, const string& orig_pattern) ...@@ -1218,6 +1356,17 @@ get_watermark (const string& infile, const string& orig_pattern)
return 1; return 1;
} }
if (Params::test_truncate)
{
const size_t want_n_samples = wav_data.sample_rate() * wav_data.n_channels() * Params::test_truncate;
vector<float> short_samples = wav_data.samples();
if (want_n_samples < short_samples.size())
{
short_samples.resize (want_n_samples);
wav_data.set_samples (short_samples);
}
}
if (wav_data.sample_rate() == Params::mark_sample_rate) if (wav_data.sample_rate() == Params::mark_sample_rate)
{ {
return decode_and_report (wav_data, orig_pattern); return decode_and_report (wav_data, orig_pattern);
...@@ -1228,34 +1377,6 @@ get_watermark (const string& infile, const string& orig_pattern) ...@@ -1228,34 +1377,6 @@ get_watermark (const string& infile, const string& orig_pattern)
} }
} }
int
get_watermark_delta (const string& origfile, const string& infile, const string& orig_pattern)
{
WavData orig_wav_data;
if (!orig_wav_data.load (origfile))
{
fprintf (stderr, "audiowmark: error loading %s: %s\n", origfile.c_str(), orig_wav_data.error_blurb());
return 1;
}
WavData wav_data;
if (!wav_data.load (infile))
{
fprintf (stderr, "audiowmark: error loading %s: %s\n", infile.c_str(), wav_data.error_blurb());
return 1;
}
/*
vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data);
vector<vector<complex<float>>> fft_orig_out = compute_frame_ffts (orig_wav_data);
return decode_and_report (wav_data, orig_pattern, fft_out, fft_orig_out);
*/
/* FIXME? */
printf ("delta decoding currently not supported\n");
return 1;
}
int int
gentest (const string& infile, const string& outfile) gentest (const string& infile, const string& outfile)
{ {
...@@ -1364,14 +1485,6 @@ main (int argc, char **argv) ...@@ -1364,14 +1485,6 @@ main (int argc, char **argv)
{ {
cut_start (argv[2], argv[3], argv[4]); cut_start (argv[2], argv[3], argv[4]);
} }
else if (op == "get-delta" && argc == 4)
{
return get_watermark_delta (argv[2], argv[3], /* no ber */ "");
}
else if (op == "cmp-delta" && argc == 5)
{
return get_watermark_delta (argv[2], argv[3], argv[4]);
}
else if (op == "gen-key" && argc == 3) else if (op == "gen-key" && argc == 3)
{ {
return gen_key (argv[2]); return gen_key (argv[2]);
......
#!/bin/bash #!/bin/bash
TRANSFORM=$1 TRANSFORM=$1
if [ "x$AWM_TRUNCATE" != "x" ]; then
AWM_REPORT=truncv
fi
if [ "x$AWM_SET" == "x" ]; then if [ "x$AWM_SET" == "x" ]; then
AWM_SET=small AWM_SET=small
fi fi
...@@ -29,7 +32,7 @@ fi ...@@ -29,7 +32,7 @@ fi
do do
for SEED in $AWM_SEEDS for SEED in $AWM_SEEDS
do do
echo $i echo in_file $i
if [ "x$AWM_RAND_PATTERN" != "x" ]; then if [ "x$AWM_RAND_PATTERN" != "x" ]; then
# random pattern, 128 bit # random pattern, 128 bit
...@@ -43,12 +46,14 @@ do ...@@ -43,12 +46,14 @@ do
# pseudo random pattern, 128 bit # pseudo random pattern, 128 bit
PATTERN=4e1243bd22c66e76c2ba9eddc1f91394 PATTERN=4e1243bd22c66e76c2ba9eddc1f91394
fi fi
echo in_pattern $PATTERN
echo in_flags $AWM_PARAMS --test-key $SEED
audiowmark add "$i" ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --test-key $SEED >/dev/null audiowmark add "$i" ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --test-key $SEED >/dev/null
if [ "x$AWM_RAND_CUT" != x ]; then if [ "x$AWM_RAND_CUT" != x ]; then
CUT=$RANDOM CUT=$RANDOM
audiowmark cut-start "${AWM_FILE}.wav" "${AWM_FILE}.wav" $CUT audiowmark cut-start "${AWM_FILE}.wav" "${AWM_FILE}.wav" $CUT
TEST_CUT_ARGS="--test-cut $CUT" TEST_CUT_ARGS="--test-cut $CUT"
echo in_cut $CUT
else else
TEST_CUT_ARGS="" TEST_CUT_ARGS=""
fi fi
...@@ -85,18 +90,47 @@ do ...@@ -85,18 +90,47 @@ do
echo "unknown transform $TRANSFORM" >&2 echo "unknown transform $TRANSFORM" >&2
exit 1 exit 1
fi fi
# blind decoding echo
audiowmark cmp $OUT_FILE $PATTERN $AWM_PARAMS --test-key $SEED $TEST_CUT_ARGS if [ "x$AWM_REPORT" == "xtruncv" ]; then
# decoding with original for TRUNC in $AWM_TRUNCATE
# audiowmark cmp-delta "$i" t.wav $PATTERN $AWM_PARAMS --test-key $SEED do
audiowmark cmp $OUT_FILE $PATTERN $AWM_PARAMS --test-key $SEED $TEST_CUT_ARGS --test-truncate $TRUNC | sed "s/^/$TRUNC /g"
echo
done
else
audiowmark cmp $OUT_FILE $PATTERN $AWM_PARAMS --test-key $SEED $TEST_CUT_ARGS
echo
fi
rm -f ${AWM_FILE}.wav $OUT_FILE # cleanup temp files
done done
done | { done | {
if [ "x$AWM_REPORT" == "xfer" ]; then if [ "x$AWM_REPORT" == "xfer" ]; then
awk 'BEGIN { bad = n = 0 } $1 == "match_count" { if ($2 == 0) bad++; n++; } END { print bad, n, bad * 100.0 / n; }' awk 'BEGIN { bad = n = 0 } $1 == "match_count" { if ($2 == 0) bad++; n++; } END { print bad, n, bad * 100.0 / n; }'
elif [ "x$AWM_REPORT" == "xferv" ]; then
awk 'BEGIN { bad = n = 0 } { print "###", $0; } $1 == "match_count" { if ($2 == 0) bad++; n++; } END { print bad, n, bad * 100.0 / n; }'
elif [ "x$AWM_REPORT" == "xsync" ]; then elif [ "x$AWM_REPORT" == "xsync" ]; then
awk 'BEGIN { bad = n = 0 } $1 == "sync_match" { bad += (3 - $2) / 3.0; n++; } END { print bad, n, bad * 100.0 / n; }' awk 'BEGIN { bad = n = 0 } $1 == "sync_match" { bad += (3 - $2) / 3.0; n++; } END { print bad, n, bad * 100.0 / n; }'
elif [ "x$AWM_REPORT" == "xsyncv" ]; then elif [ "x$AWM_REPORT" == "xsyncv" ]; then
awk '{ print "###", $0; } $1 == "sync_match" { correct += $2; missing += 3 - $2; incorrect += $3-$2; print "correct:", correct, "missing:", missing, "incorrect:", incorrect; }' awk '{ print "###", $0; } $1 == "sync_match" { correct += $2; missing += 3 - $2; incorrect += $3-$2; print "correct:", correct, "missing:", missing, "incorrect:", incorrect; }'
elif [ "x$AWM_REPORT" == "xtruncv" ]; then
awk ' {
print "###", $0;
}
$2 == "match_count" {
if (!n[$1])
{
n[$1] = 0;
bad[$1] = 0;
}
if ($3 == 0)
bad[$1]++;
n[$1]++;
}
END {
for (trunc in n) {
print trunc, bad[trunc], n[trunc], bad[trunc] * 100.0 / n[trunc];
}
}'
else else
echo "unknown report $AWM_REPORT" >&2 echo "unknown report $AWM_REPORT" >&2
exit 1 exit 1
......
...@@ -22,11 +22,14 @@ parity (unsigned int v) ...@@ -22,11 +22,14 @@ parity (unsigned int v)
return p; return p;
} }
// rate 1/6 code generator poynomial from "In search of a 2dB Coding Gain", Yuen and Vo constexpr auto ab_generators = std::array<unsigned,12>
// minimum free distance 56 {
constexpr unsigned int rate = 6; 066561, 075211, 071545, 054435, 063635, 052475,
constexpr unsigned int order = 15; 063543, 075307, 052547, 045627, 067657, 051757
constexpr auto generators = std::array<unsigned,6> { 046321, 051271, 070535, 063667, 073277, 076531 }; };
constexpr unsigned int ab_rate = ab_generators.size();
constexpr unsigned int order = 15;
/* /*
constexpr unsigned int order = 9; constexpr unsigned int order = 9;
...@@ -43,14 +46,45 @@ constexpr unsigned int state_count = (1 << order); ...@@ -43,14 +46,45 @@ constexpr unsigned int state_count = (1 << order);
constexpr unsigned int state_mask = (1 << order) - 1; constexpr unsigned int state_mask = (1 << order) - 1;
size_t size_t
conv_code_size (size_t msg_size) conv_code_size (ConvBlockType block_type, size_t msg_size)
{
switch (block_type)
{
case ConvBlockType::a:
case ConvBlockType::b: return (msg_size + order) * ab_rate / 2;
case ConvBlockType::ab: return (msg_size + order) * ab_rate;
default: assert (false);
}
}
vector<unsigned>
get_block_type_generators (ConvBlockType block_type)
{ {
return (msg_size + order) * rate; vector<unsigned> generators;
if (block_type == ConvBlockType::a)
{
for (unsigned int i = 0; i < ab_rate / 2; i++)
generators.push_back (ab_generators[i * 2]);
}
else if (block_type == ConvBlockType::b)
{
for (unsigned int i = 0; i < ab_rate / 2; i++)
generators.push_back (ab_generators[i * 2 + 1]);
}
else
{
assert (block_type == ConvBlockType::ab);
generators.assign (ab_generators.begin(), ab_generators.end());
}
return generators;
} }
vector<int> vector<int>
conv_encode (const vector<int>& in_bits) conv_encode (ConvBlockType block_type, const vector<int>& in_bits)
{ {
auto generators = get_block_type_generators (block_type);
vector<int> out_vec; vector<int> out_vec;
vector<int> vec = in_bits; vector<int> vec = in_bits;
...@@ -75,8 +109,10 @@ conv_encode (const vector<int>& in_bits) ...@@ -75,8 +109,10 @@ conv_encode (const vector<int>& in_bits)
/* decode using viterbi algorithm */ /* decode using viterbi algorithm */
vector<int> vector<int>
conv_decode_soft (const vector<float>& coded_bits, float *error_out) conv_decode_soft (ConvBlockType block_type, const vector<float>& coded_bits, float *error_out)
{ {
auto generators = get_block_type_generators (block_type);
unsigned int rate = generators.size();
vector<int> decoded_bits; vector<int> decoded_bits;
assert (coded_bits.size() % rate == 0); assert (coded_bits.size() % rate == 0);
...@@ -121,7 +157,7 @@ conv_decode_soft (const vector<float>& coded_bits, float *error_out) ...@@ -121,7 +157,7 @@ conv_decode_soft (const vector<float>& coded_bits, float *error_out)
float delta = old_table[state].delta; float delta = old_table[state].delta;
int sbit_pos = new_state * rate; int sbit_pos = new_state * rate;
for (size_t p = 0; p < generators.size(); p++) for (size_t p = 0; p < rate; p++)
{ {
const float cbit = coded_bits[i + p]; const float cbit = coded_bits[i + p];
const float sbit = state2bits[sbit_pos + p]; const float sbit = state2bits[sbit_pos + p];
...@@ -160,7 +196,7 @@ conv_decode_soft (const vector<float>& coded_bits, float *error_out) ...@@ -160,7 +196,7 @@ conv_decode_soft (const vector<float>& coded_bits, float *error_out)
} }
vector<int> vector<int>
conv_decode_hard (const vector<int>& coded_bits) conv_decode_hard (ConvBlockType block_type, const vector<int>& coded_bits)
{ {
/* for the final application, we always want soft decoding, so we don't /* for the final application, we always want soft decoding, so we don't
* special case hard decoding here, so this will be a little slower than * special case hard decoding here, so this will be a little slower than
...@@ -170,5 +206,24 @@ conv_decode_hard (const vector<int>& coded_bits) ...@@ -170,5 +206,24 @@ conv_decode_hard (const vector<int>& coded_bits)
for (auto b : coded_bits) for (auto b : coded_bits)
soft_bits.push_back (b ? 1.0f : 0.0f); soft_bits.push_back (b ? 1.0f : 0.0f);
return conv_decode_soft (soft_bits); return conv_decode_soft (block_type, soft_bits);
}
void
conv_print_table (ConvBlockType block_type)
{
vector<int> bits (100);
bits[0] = 1;
vector<int> out_bits = conv_encode (block_type, bits);
auto generators = get_block_type_generators (block_type);
unsigned int rate = generators.size();
for (unsigned int r = 0; r < rate; r++)
{
for (unsigned int i = 0; i < order; i++)
printf ("%s%d", i == 0 ? "" : " ", out_bits[i * rate + r]);
printf ("\n");
}
} }
...@@ -4,9 +4,13 @@ ...@@ -4,9 +4,13 @@
#include <vector> #include <vector>
#include <string> #include <string>
size_t conv_code_size (size_t msg_size); enum class ConvBlockType { a, b, ab };
std::vector<int> conv_encode (const std::vector<int>& in_bits);
std::vector<int> conv_decode_hard (const std::vector<int>& coded_bits); size_t conv_code_size (ConvBlockType block_type, size_t msg_size);
std::vector<int> conv_decode_soft (const std::vector<float>& coded_bits, float *error_out = nullptr); std::vector<int> conv_encode (ConvBlockType block_type, const std::vector<int>& in_bits);
std::vector<int> conv_decode_hard (ConvBlockType block_type, const std::vector<int>& coded_bits);
std::vector<int> conv_decode_soft (ConvBlockType block_type, const std::vector<float>& coded_bits, float *error_out = nullptr);
void conv_print_table (ConvBlockType block_type);
#endif /* AUDIOWMARK_CONV_CODE_HH */ #endif /* AUDIOWMARK_CONV_CODE_HH */
...@@ -4,10 +4,9 @@ echo ".sync-codec-resistence" ...@@ -4,10 +4,9 @@ echo ".sync-codec-resistence"
echo '[frame="topbot",options="header",cols="<2,6*>1"]' echo '[frame="topbot",options="header",cols="<2,6*>1"]'
echo '|==========================' echo '|=========================='
echo -n "| " echo -n "| "
for D in $(seq 10 -1 5) for STRENGTH in $(seq 10 -1 5)
do do
DELTA=$(printf "0.0%02d\n" $D) echo -n "| $STRENGTH"
echo -n "| $DELTA"
done done
echo echo
for TEST in mp3 double-mp3 ogg for TEST in mp3 double-mp3 ogg
...@@ -22,10 +21,9 @@ do ...@@ -22,10 +21,9 @@ do
echo "error: bad TEST $TEST ???" echo "error: bad TEST $TEST ???"
exit 1 exit 1
fi fi
for D in $(seq 10 -1 5) for STRENGTH in $(seq 10 -1 5)
do do
DELTA=$(printf "0.0%02d\n" $D) cat $STRENGTH-$TEST-* | grep -v '^#' | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}'
cat $DELTA-$TEST-* | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}'
done done
echo echo
done done
......
#!/bin/bash #!/bin/bash
DELTA_RANGE="0.005 0.006 0.007 0.008 0.009 0.010" STRENGTH_RANGE=$(seq 5 10)
SEEDS="$(seq 0 19)" SEEDS="$(seq 0 19)"
echo -n "all: " echo -n "all: "
for SEED in $SEEDS for SEED in $SEEDS
do do
for DELTA in $DELTA_RANGE for STRENGTH in $STRENGTH_RANGE
do do
echo -n "$DELTA-ogg-$SEED $DELTA-mp3-$SEED $DELTA-double-mp3-$SEED " echo -n "$STRENGTH-ogg-$SEED $STRENGTH-mp3-$SEED $STRENGTH-double-mp3-$SEED "
done done
done done
...@@ -17,23 +17,23 @@ echo ...@@ -17,23 +17,23 @@ echo
for SEED in $SEEDS for SEED in $SEEDS
do do
for DELTA in $DELTA_RANGE for STRENGTH in $STRENGTH_RANGE
do do
FILE="$DELTA-ogg-$SEED" FILE="$STRENGTH-ogg-$SEED"
echo "$FILE:" echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh ogg 128 ) >x$FILE" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_REPORT=ferv AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh ogg 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE" echo -e "\tmv x$FILE $FILE"
echo echo
FILE="$DELTA-mp3-$SEED" FILE="$STRENGTH-mp3-$SEED"
echo "$FILE:" echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_REPORT=ferv AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE" echo -e "\tmv x$FILE $FILE"
echo echo
FILE="$DELTA-double-mp3-$SEED" FILE="$STRENGTH-double-mp3-$SEED"
echo "$FILE:" echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh double-mp3 128 ) >x$FILE" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_REPORT=ferv AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh double-mp3 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE" echo -e "\tmv x$FILE $FILE"
echo echo
done done
......
#!/bin/bash
TRUNCATE="60 110 245"
for TRUNC in 60 110 245
do
echo ".sync-codec-resistence$TRUNC"
echo '[frame="topbot",options="header",cols="<2,6*>1"]'
echo '|=========================='
echo -n "| "
for STRENGTH in $(seq 10 -1 5)
do
echo -n "| $STRENGTH"
done
echo
for TEST in mp3 double-mp3 ogg
do
if [ $TEST == mp3 ]; then
echo -n "| mp3 128kbit/s"
elif [ $TEST == double-mp3 ]; then
echo -n "| double mp3 128kbit/s"
elif [ $TEST == ogg ]; then
echo -n "| ogg 128kbit/s"
else
echo "error: bad TEST $TEST ???"
exit 1
fi
for STRENGTH in $(seq 10 -1 5)
do
cat $STRENGTH-$TEST-* | grep -v '^#' | grep ^$TRUNC | awk '{bad += $2; n += $3} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}'
done
echo
done
echo
echo '|=========================='
done
#!/bin/bash
STRENGTH_RANGE=$(seq 5 10)
SEEDS="$(seq 0 19)"
TRUNCATE="60 110 245"
echo -n "all: "
for SEED in $SEEDS
do
for STRENGTH in $STRENGTH_RANGE
do
echo -n "$STRENGTH-ogg-$SEED $STRENGTH-mp3-$SEED $STRENGTH-double-mp3-$SEED "
done
done
echo
echo
for SEED in $SEEDS
do
for STRENGTH in $STRENGTH_RANGE
do
FILE="$STRENGTH-ogg-$SEED"
echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_TRUNCATE='$TRUNCATE' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh ogg 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE"
echo
FILE="$STRENGTH-mp3-$SEED"
echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_TRUNCATE='$TRUNCATE' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE"
echo
FILE="$STRENGTH-double-mp3-$SEED"
echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_TRUNCATE='$TRUNCATE' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh double-mp3 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE"
echo
done
done
...@@ -10,6 +10,31 @@ using std::vector; ...@@ -10,6 +10,31 @@ using std::vector;
using std::regex; using std::regex;
using std::regex_match; using std::regex_match;
static void
gcrypt_init()
{
static bool init_ok = false;
if (!init_ok)
{
/* version check: start libgcrypt initialization */
if (!gcry_check_version (GCRYPT_VERSION))
{
fprintf (stderr, "audiowmark: libgcrypt version mismatch\n");
exit (1);
}
/* disable secure memory (assume we run in a controlled environment) */
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
/* tell libgcrypt that initialization has completed */
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
init_ok = true;
}
}
static vector<unsigned char> aes_key (16); // 128 bits static vector<unsigned char> aes_key (16); // 128 bits
static constexpr auto GCRY_CIPHER = GCRY_CIPHER_AES128; static constexpr auto GCRY_CIPHER = GCRY_CIPHER_AES128;
...@@ -55,6 +80,8 @@ print (const string& label, const vector<unsigned char>& data) ...@@ -55,6 +80,8 @@ print (const string& label, const vector<unsigned char>& data)
Random::Random (uint64_t seed, Stream stream) Random::Random (uint64_t seed, Stream stream)
{ {
gcrypt_init();
vector<unsigned char> ctr = get_start_counter (seed, stream); vector<unsigned char> ctr = get_start_counter (seed, stream);
// print ("CTR", ctr); // print ("CTR", ctr);
...@@ -201,6 +228,8 @@ Random::load_global_key (const string& key_file) ...@@ -201,6 +228,8 @@ Random::load_global_key (const string& key_file)
string string
Random::gen_key() Random::gen_key()
{ {
gcrypt_init();
vector<unsigned char> key (16); vector<unsigned char> key (16);
gcry_randomize (&key[0], 16, /* long term key material strength */ GCRY_VERY_STRONG_RANDOM); gcry_randomize (&key[0], 16, /* long term key material strength */ GCRY_VERY_STRONG_RANDOM);
return vec_to_hex_str (key); return vec_to_hex_str (key);
......
...@@ -15,7 +15,8 @@ public: ...@@ -15,7 +15,8 @@ public:
sync_up_down = 2, sync_up_down = 2,
pad_up_down = 3, pad_up_down = 3,
mix = 4, mix = 4,
bit_order = 5 bit_order = 5,
frame_position = 6
}; };
private: private:
gcry_cipher_hd_t aes_ctr_cipher; gcry_cipher_hd_t aes_ctr_cipher;
......
...@@ -34,10 +34,36 @@ generate_error_vector (size_t n, int errors) ...@@ -34,10 +34,36 @@ generate_error_vector (size_t n, int errors)
} }
return ev; return ev;
} }
static bool
no_case_equal (const string& s1, const string& s2)
{
if (s1.size() != s2.size())
return false;
return std::equal (s1.begin(), s1.end(), s2.begin(),
[] (char c1, char c2) -> bool { return tolower (c1) == tolower (c2);});
}
int int
main (int argc, char **argv) main (int argc, char **argv)
{ {
if (argc == 1) string btype = (argc > 1) ? argv[1] : "";
ConvBlockType block_type;
if (no_case_equal (btype, "A"))
block_type = ConvBlockType::a;
else if (no_case_equal (btype, "B"))
block_type = ConvBlockType::b;
else if (no_case_equal (btype, "AB"))
block_type = ConvBlockType::ab;
else
{
printf ("first argument must be A, B, or AB\n");
return 1;
}
if (argc == 2)
{ {
vector<int> in_bits = bit_str_to_vec ("80f12381"); vector<int> in_bits = bit_str_to_vec ("80f12381");
...@@ -46,16 +72,16 @@ main (int argc, char **argv) ...@@ -46,16 +72,16 @@ main (int argc, char **argv)
printf ("%d", b); printf ("%d", b);
printf ("\n"); printf ("\n");
vector<int> coded_bits = conv_encode (in_bits); vector<int> coded_bits = conv_encode (block_type, in_bits);
printf ("coded vector (n=%zd): ", coded_bits.size()); printf ("coded vector (n=%zd): ", coded_bits.size());
for (auto b : coded_bits) for (auto b : coded_bits)
printf ("%d", b); printf ("%d", b);
printf ("\n"); printf ("\n");
printf ("coded hex: %s\n", bit_vec_to_str (coded_bits).c_str()); printf ("coded hex: %s\n", bit_vec_to_str (coded_bits).c_str());
assert (coded_bits.size() == conv_code_size (in_bits.size())); assert (coded_bits.size() == conv_code_size (block_type, in_bits.size()));
vector<int> decoded_bits = conv_decode_hard (coded_bits); vector<int> decoded_bits = conv_decode_hard (block_type, coded_bits);
printf ("output vector (k=%zd): ", decoded_bits.size()); printf ("output vector (k=%zd): ", decoded_bits.size());
for (auto b : decoded_bits) for (auto b : decoded_bits)
printf ("%d", b); printf ("%d", b);
...@@ -68,9 +94,9 @@ main (int argc, char **argv) ...@@ -68,9 +94,9 @@ main (int argc, char **argv)
errors++; errors++;
printf ("decoding errors: %d\n", errors); printf ("decoding errors: %d\n", errors);
} }
if (argc == 2 && string (argv[1]) == "error") if (argc == 3 && string (argv[2]) == "error")
{ {
size_t max_bit_errors = conv_code_size (128) * 0.5; size_t max_bit_errors = conv_code_size (block_type, 128) * 0.5;
for (size_t bit_errors = 0; bit_errors < max_bit_errors; bit_errors++) for (size_t bit_errors = 0; bit_errors < max_bit_errors; bit_errors++)
{ {
...@@ -84,14 +110,14 @@ main (int argc, char **argv) ...@@ -84,14 +110,14 @@ main (int argc, char **argv)
while (in_bits.size() != 128) while (in_bits.size() != 128)
in_bits.push_back (rand() & 1); in_bits.push_back (rand() & 1);
vector<int> coded_bits = conv_encode (in_bits); vector<int> coded_bits = conv_encode (block_type, in_bits);
coded_bit_count = coded_bits.size(); coded_bit_count = coded_bits.size();
vector<int> error_bits = generate_error_vector (coded_bits.size(), bit_errors); vector<int> error_bits = generate_error_vector (coded_bits.size(), bit_errors);
for (size_t pos = 0; pos < coded_bits.size(); pos++) for (size_t pos = 0; pos < coded_bits.size(); pos++)
coded_bits[pos] ^= error_bits[pos]; coded_bits[pos] ^= error_bits[pos];
vector<int> decoded_bits = conv_decode_hard (coded_bits); vector<int> decoded_bits = conv_decode_hard (block_type, coded_bits);
assert (decoded_bits.size() == 128); assert (decoded_bits.size() == 128);
...@@ -105,7 +131,7 @@ main (int argc, char **argv) ...@@ -105,7 +131,7 @@ main (int argc, char **argv)
printf ("%f %f\n", (100.0 * bit_errors) / coded_bit_count, (100.0 * bad_decode) / test_size); printf ("%f %f\n", (100.0 * bit_errors) / coded_bit_count, (100.0 * bad_decode) / test_size);
} }
} }
if (argc == 2 && string (argv[1]) == "soft-error") if (argc == 3 && string (argv[2]) == "soft-error")
{ {
for (double stddev = 0; stddev < 1.5; stddev += 0.01) for (double stddev = 0; stddev < 1.5; stddev += 0.01)
{ {
...@@ -120,7 +146,7 @@ main (int argc, char **argv) ...@@ -120,7 +146,7 @@ main (int argc, char **argv)
while (in_bits.size() != 128) while (in_bits.size() != 128)
in_bits.push_back (rand() & 1); in_bits.push_back (rand() & 1);
vector<int> coded_bits = conv_encode (in_bits); vector<int> coded_bits = conv_encode (block_type, in_bits);
coded_bit_count = coded_bits.size(); coded_bit_count = coded_bits.size();
std::default_random_engine generator; std::default_random_engine generator;
...@@ -130,7 +156,7 @@ main (int argc, char **argv) ...@@ -130,7 +156,7 @@ main (int argc, char **argv)
for (auto b : coded_bits) for (auto b : coded_bits)
recv_bits.push_back (b + dist (generator)); recv_bits.push_back (b + dist (generator));
vector<int> decoded_bits1 = conv_decode_soft (recv_bits); vector<int> decoded_bits1 = conv_decode_soft (block_type, recv_bits);
vector<int> recv_hard_bits; vector<int> recv_hard_bits;
for (auto b : recv_bits) for (auto b : recv_bits)
...@@ -139,7 +165,7 @@ main (int argc, char **argv) ...@@ -139,7 +165,7 @@ main (int argc, char **argv)
for (size_t x = 0; x < recv_hard_bits.size(); x++) for (size_t x = 0; x < recv_hard_bits.size(); x++)
local_be += coded_bits[x] ^ recv_hard_bits[x]; local_be += coded_bits[x] ^ recv_hard_bits[x];
vector<int> decoded_bits2 = conv_decode_hard (recv_hard_bits); vector<int> decoded_bits2 = conv_decode_hard (block_type, recv_hard_bits);
assert (decoded_bits1.size() == 128); assert (decoded_bits1.size() == 128);
assert (decoded_bits2.size() == 128); assert (decoded_bits2.size() == 128);
...@@ -161,7 +187,7 @@ main (int argc, char **argv) ...@@ -161,7 +187,7 @@ main (int argc, char **argv)
printf ("%f %f %f\n", double (100 * local_be) / test_size / coded_bit_count, (100.0 * bad_decode1) / test_size, (100.0 * bad_decode2) / test_size); printf ("%f %f %f\n", double (100 * local_be) / test_size / coded_bit_count, (100.0 * bad_decode1) / test_size, (100.0 * bad_decode2) / test_size);
} }
} }
if (argc == 2 && string (argv[1]) == "perf") if (argc == 3 && string (argv[2]) == "perf")
{ {
vector<int> in_bits; vector<int> in_bits;
while (in_bits.size() != 128) while (in_bits.size() != 128)
...@@ -171,9 +197,11 @@ main (int argc, char **argv) ...@@ -171,9 +197,11 @@ main (int argc, char **argv)
const size_t runs = 20; const size_t runs = 20;
for (size_t i = 0; i < runs; i++) for (size_t i = 0; i < runs; i++)
{ {
vector<int> out_bits = conv_decode_hard (conv_encode (in_bits)); vector<int> out_bits = conv_decode_hard (block_type, conv_encode (block_type, in_bits));
assert (out_bits == in_bits); assert (out_bits == in_bits);
} }
printf ("%.1f ms/block\n", (gettime() - start_t) / runs * 1000.0); printf ("%.1f ms/block\n", (gettime() - start_t) / runs * 1000.0);
} }
if (argc == 3 && string (argv[2]) == "table")
conv_print_table (block_type);
} }
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