Commit ae5853e2 authored by Stefan Westerfeld's avatar Stefan Westerfeld

Merge branch 'streaming'

parents 5c6b6c71 d16e67c1
Overview of Changes in audiowmark-0.2.0:
* support input/output streams
* support raw streams
* some performance optimizations
* unified logging and --quiet option
* improved mp3 detection to avoid false positives
* split up watermarking source (wmadd/wmget/wmcommon)
Overview of Changes in audiowmark-0.1.0:
* initial release
......@@ -150,9 +150,116 @@ watermark. Fractional strengths (like 7.5) are possible.
audiowmark add --strength 15 in.wav out.wav 0123456789abcdef0011223344556677
audiowmark get --strength 15 out.wav
== Output as Stream
Usually, an input file is read, watermarked and an output file is written.
This means that it takes some time before the watermarked file can be used.
An alternative is to output the watermarked file as stream to stdout. One use
case is sending the watermarked file to a user via network while the
watermarker is still working on the rest of the file. Here is an example how to
watermark a wav file to stdout:
audiowmark add in.wav - 0123456789abcdef0011223344556677 | play -
In this case the file in.wav is read, watermarked, and the output is sent
to stdout. The "play -" can start playing the watermarked stream while the
rest of the file is being watermarked.
If - is used as output, the output is a valid .wav file, so the programs
running after `audiowmark` will be able to determine sample rate, number of
channels, bit depth, encoding and so on from the wav header.
Note that all input formats supported by audiowmark can be used in this way,
for instance flac/mp3:
audiowmark add in.flac - 0123456789abcdef0011223344556677 | play -
audiowmark add in.mp3 - 0123456789abcdef0011223344556677 | play -
== Input from Stream
Similar to the output, the `audiowmark` input can be a stream. In this case,
the input must be a valid .wav file. The watermarker will be able to
start watermarking the input stream before all data is available. An
example would be:
cat in.wav | audiowmark add - out.wav 0123456789abcdef0011223344556677
It is possible to do both, input from stream and output as stream.
cat in.wav | audiowmark add - - 0123456789abcdef0011223344556677 | play -
Streaming input is also supported for watermark detection.
cat in.wav | audiowmark get -
== Raw Streams
So far, all streams described here are essentially wav streams, which means
that the wav header allows `audiowmark` to determine sample rate, number of
channels, bit depth, encoding and so forth from the stream itself, and the a
wav header is written for the program after `audiowmark`, so that this can
figure out the parameters of the stream.
There are two cases where this is problematic. The first case is if the full
length of the stream is not known at the time processing starts. Then a wav
header cannot be used, as the wav file contains the length of the stream. The
second case is that the program before or after `audiowmark` doesn't support wav
headers.
For these two cases, raw streams are available. The idea is to set all
information that is needed like sample rate, number of channels,... manually.
Then, headerless data can be processed from stdin and/or sent to stdout.
--input-format raw::
--output-format raw::
--format raw::
These can be used to set the input format or output format to raw. The
last version sets both, input and output format to raw.
--raw-rate <rate>::
This should be used to set the sample rate. The input sample rate and
the output sample rate will always be the same (no resampling is
done by the watermarker). There is no default for the sampling rate,
so this parameter must always be specified for raw streams.
--raw-input-bits <bits>::
--raw-output-bits <bits>::
--raw-bits <bits>::
The options can be used to set the input number of bits, the output number
of bits or both. The number of bits can either be `16` or `24`. The default
number of bits is `16`.
--raw-input-endian <endian>::
--raw-output-endian <endian>::
--raw-endian <endian>::
These options can be used to set the input/output endianness or both.
The <endian> parameter can either be `little` or `big`. The default
endianness is `little`.
--raw-input-encoding <encoding>::
--raw-output-encoding <encoding>::
--raw-encoding <encoding>::
These options can be used to set the input/output encoding or both.
The <encoding> parameter can either be `signed` or `unsigned`. The
default encoding is `signed`.
--raw-channels <channels>::
This can be used to set the number of channels. Note that the number
of input channels and the number of output channels must always be the
same. The watermarker has been designed and tested for stereo files,
so the number of channels should really be `2`. This is also the
default.
== Dependencies
If you compile from source, audiowmark needs the following libraries:
If you compile from source, `audiowmark` needs the following libraries:
* libfftw3
* libsndfile
......@@ -162,7 +269,7 @@ If you compile from source, audiowmark needs the following libraries:
== Building fftw
audiowmark needs the single prevision variant of fftw3.
`audiowmark` needs the single prevision variant of fftw3.
If you are building fftw3 from source, use the `--enable-float`
configure parameter to build it, e.g.::
......@@ -181,7 +288,7 @@ or, when building from git
== Docker Build
You should be able to execute audiowmark via Docker.
You should be able to execute `audiowmark` via Docker.
Example that outputs the usage message:
docker build -t audiowmark .
......
AC_INIT([audiowmark], [0.1.0])
AC_INIT([audiowmark], [0.2.0])
AC_CONFIG_SRCDIR([src/audiowmark.cc])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
......
......@@ -6,3 +6,4 @@ audiowmark
testconvcode
testmp3
testrandom
teststream
bin_PROGRAMS = audiowmark
COMMON_SRC = utils.hh utils.cc convcode.hh convcode.cc random.hh random.cc mp3.cc mp3.hh wavdata.cc wavdata.hh
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
COMMON_LIBS = $(SNDFILE_LIBS) $(FFTW_LIBS) $(LIBGCRYPT_LIBS) $(LIBMPG123_LIBS)
audiowmark_SOURCES = audiowmark.cc fft.cc fft.hh $(COMMON_SRC)
audiowmark_SOURCES = audiowmark.cc wmget.cc wmadd.cc $(COMMON_SRC)
audiowmark_LDFLAGS = $(COMMON_LIBS)
noinst_PROGRAMS = testconvcode testrandom testmp3
noinst_PROGRAMS = testconvcode testrandom testmp3 teststream
testconvcode_SOURCES = testconvcode.cc $(COMMON_SRC)
testconvcode_LDFLAGS = $(COMMON_LIBS)
......@@ -16,3 +19,6 @@ testrandom_LDFLAGS = $(COMMON_LIBS)
testmp3_SOURCES = testmp3.cc $(COMMON_SRC)
testmp3_LDFLAGS = $(COMMON_LIBS)
teststream_SOURCES = teststream.cc $(COMMON_SRC)
teststream_LDFLAGS = $(COMMON_LIBS)
possible improvements:
- dynamic bit strength
- mp3 support with libmad
streaming:
- final documentation
- merge mp3 code
- fix virtual constructor FIXMEs
#include "audiostream.hh"
#include "wmcommon.hh"
#include "sfinputstream.hh"
#include "sfoutputstream.hh"
#include "mp3inputstream.hh"
#include "rawconverter.hh"
#include "rawoutputstream.hh"
#include "stdoutwavoutputstream.hh"
using std::string;
AudioStream::~AudioStream()
{
}
std::unique_ptr<AudioInputStream>
AudioInputStream::create (const string& filename, Error& err)
{
std::unique_ptr<AudioInputStream> in_stream;
if (Params::input_format == Format::AUTO)
{
SFInputStream *sistream = new SFInputStream();
in_stream.reset (sistream);
err = sistream->open (filename);
if (err && MP3InputStream::detect (filename))
{
MP3InputStream *mistream = new MP3InputStream();
in_stream.reset (mistream);
err = mistream->open (filename);
if (err)
return nullptr;
}
else if (err)
return nullptr;
}
else
{
RawInputStream *ristream = new RawInputStream();
in_stream.reset (ristream);
err = ristream->open (filename, Params::raw_input_format);
if (err)
return nullptr;
}
return in_stream;
}
std::unique_ptr<AudioOutputStream>
AudioOutputStream::create (const string& filename, int n_channels, int sample_rate, int bit_depth, size_t n_frames, Error& err)
{
std::unique_ptr<AudioOutputStream> out_stream;
if (Params::output_format == Format::RAW)
{
RawOutputStream *rostream = new RawOutputStream();
out_stream.reset (rostream);
err = rostream->open (filename, Params::raw_output_format);
if (err)
return nullptr;
}
else if (filename == "-")
{
StdoutWavOutputStream *swstream = new StdoutWavOutputStream();
out_stream.reset (swstream);
err = swstream->open (n_channels, sample_rate, bit_depth, n_frames);
if (err)
return nullptr;
}
else
{
SFOutputStream *sfostream = new SFOutputStream();
out_stream.reset (sfostream);
err = sfostream->open (filename, n_channels, sample_rate, bit_depth, n_frames);
if (err)
return nullptr;
}
return out_stream;
}
#ifndef AUDIOWMARK_AUDIO_STREAM_HH
#define AUDIOWMARK_AUDIO_STREAM_HH
#include <vector>
#include <memory>
#include "utils.hh"
class AudioStream
{
public:
virtual int bit_depth() const = 0;
virtual int sample_rate() const = 0;
virtual int n_channels() const = 0;
virtual ~AudioStream();
};
class AudioInputStream : public AudioStream
{
public:
static std::unique_ptr<AudioInputStream> create (const std::string& filename, Error& err);
// for streams that do not know the number of frames in advance (i.e. raw input stream)
static constexpr size_t N_FRAMES_UNKNOWN = ~size_t (0);
virtual size_t n_frames() const = 0;
virtual Error read_frames (std::vector<float>& samples, size_t count) = 0;
};
class AudioOutputStream : public AudioStream
{
public:
static std::unique_ptr<AudioOutputStream> create (const std::string& filename,
int n_channels, int sample_rate, int bit_depth, size_t n_frames, Error& err);
virtual Error write_frames (const std::vector<float>& frames) = 0;
virtual Error close() = 0;
};
#endif /* AUDIOWMARK_AUDIO_STREAM_HH */
This diff is collapsed.
#ifndef AUDIOWMARK_MP3_HH
#define AUDIOWMARK_MP3_HH
#include <string>
#include "wavdata.hh"
bool mp3_detect (const std::string& filename);
std::string mp3_load (const std::string& filename, WavData& wav_data);
#endif /* AUDIOWMARK_MP3_HH */
#include "mp3.hh"
#include "mp3inputstream.hh"
#include <mpg123.h>
#include <assert.h>
#include <stdio.h>
#include <vector>
using std::vector;
using std::min;
using std::string;
struct ScopedMHandle
{
mpg123_handle *mh = nullptr;
bool need_close = false;
~ScopedMHandle()
{
if (mh && need_close)
mpg123_close (mh);
if (mh)
mpg123_delete (mh);
}
};
void
static void
mp3_init()
{
static bool mpg123_init_ok = false;
......@@ -32,152 +15,238 @@ mp3_init()
int err = mpg123_init();
if (err != MPG123_OK)
{
fprintf (stderr, "audiowmark: init mpg123 lib failed\n");
error ("audiowmark: init mpg123 lib failed\n");
exit (1);
}
mpg123_init_ok = true;
}
}
/* there is no really simple way of detecting if something is an mp3
*
* so we try to decode a few frames; if that works without error the
* file is probably a valid mp3
*/
bool
mp3_detect (const string& filename)
MP3InputStream::~MP3InputStream()
{
close();
}
Error
MP3InputStream::open (const string& filename)
{
int err = 0;
mp3_init();
mpg123_handle *mh = mpg123_new (NULL, &err);
m_handle = mpg123_new (nullptr, &err);
if (err != MPG123_OK)
return false;
return Error ("mpg123_new failed");
auto smh = ScopedMHandle { mh }; // cleanup on return
err = mpg123_param (m_handle, MPG123_ADD_FLAGS, MPG123_QUIET, 0);
if (err != MPG123_OK)
return Error ("setting quiet mode failed");
err = mpg123_param (mh, MPG123_ADD_FLAGS, MPG123_QUIET, 0);
// allow arbitary amount of data for resync */
err = mpg123_param (m_handle, MPG123_RESYNC_LIMIT, -1, 0);
if (err != MPG123_OK)
return false;
return Error ("setting resync limit parameter failed");
err = mpg123_open (mh, filename.c_str());
// force floating point output
{
const long *rates;
size_t rate_count;
mpg123_format_none (m_handle);
mpg123_rates (&rates, &rate_count);
for (size_t i = 0; i < rate_count; i++)
{
err = mpg123_format (m_handle, rates[i], MPG123_MONO|MPG123_STEREO, MPG123_ENC_FLOAT_32);
if (err != MPG123_OK)
return Error (mpg123_strerror (m_handle));
}
}
err = mpg123_open (m_handle, filename.c_str());
if (err != MPG123_OK)
return false;
return Error (mpg123_strerror (m_handle));
smh.need_close = true;
m_need_close = true;
/* scan headers to get best possible length estimate */
err = mpg123_scan (m_handle);
if (err != MPG123_OK)
return Error (mpg123_strerror (m_handle));
long rate;
int channels;
int encoding;
err = mpg123_getformat (mh, &rate, &channels, &encoding);
err = mpg123_getformat (m_handle, &rate, &channels, &encoding);
if (err != MPG123_OK)
return false;
return Error (mpg123_strerror (m_handle));
size_t buffer_bytes = mpg123_outblock (mh);
unsigned char buffer[buffer_bytes];
/* ensure that the format will not change */
mpg123_format_none (m_handle);
mpg123_format (m_handle, rate, channels, encoding);
m_n_values = mpg123_length (m_handle) * channels;
m_n_channels = channels;
m_sample_rate = rate;
m_frames_left = m_n_values / m_n_channels;
for (size_t i = 0; i < 10; i++)
return Error::Code::NONE;
}
Error
MP3InputStream::read_frames (std::vector<float>& samples, size_t count)
{
while (!m_eof && m_read_buffer.size() < count * m_n_channels)
{
size_t buffer_bytes = mpg123_outblock (m_handle);
assert (buffer_bytes % sizeof (float) == 0);
std::vector<float> buffer (buffer_bytes / sizeof (float));
size_t done;
err = mpg123_read (mh, buffer, buffer_bytes, &done);
if (err == MPG123_DONE)
int err = mpg123_read (m_handle, reinterpret_cast<unsigned char *> (&buffer[0]), buffer_bytes, &done);
if (err == MPG123_OK)
{
return true;
const size_t n_values = done / sizeof (float);
m_read_buffer.insert (m_read_buffer.end(), buffer.begin(), buffer.begin() + n_values);
}
else if (err != MPG123_OK)
else if (err == MPG123_DONE)
{
return false;
m_eof = true;
}
else if (err == MPG123_NEED_MORE)
{
// some mp3s have this error before reaching eof -> harmless
m_eof = true;
}
else
{
return Error (mpg123_strerror (m_handle));
}
}
return true;
/* pad zero samples at end if necessary to match the number of frames we promised to deliver */
if (m_eof && m_read_buffer.size() < m_frames_left * m_n_channels)
m_read_buffer.resize (m_frames_left * m_n_channels);
/* never read past the promised number of frames */
if (count > m_frames_left)
count = m_frames_left;
const auto begin = m_read_buffer.begin();
const auto end = begin + min (count * m_n_channels, m_read_buffer.size());
samples.assign (begin, end);
m_read_buffer.erase (begin, end);
m_frames_left -= count;
return Error::Code::NONE;
}
void
MP3InputStream::close()
{
if (m_state == State::OPEN)
{
if (m_handle && m_need_close)
mpg123_close (m_handle);
if (m_handle)
{
mpg123_delete (m_handle);
m_handle = nullptr;
}
m_state = State::CLOSED;
}
}
string
mp3_load (const string& filename, WavData& wav_data)
int
MP3InputStream::bit_depth() const
{
return 24; /* mp3 decoder is running on floats */
}
int
MP3InputStream::sample_rate() const
{
return m_sample_rate;
}
int
MP3InputStream::n_channels() const
{
return m_n_channels;
}
size_t
MP3InputStream::n_frames() const
{
return m_n_values / m_n_channels;
}
/* there is no really simple way of detecting if something is an mp3
*
* so we try to decode a few frames; if that works without error the
* file is probably a valid mp3
*/
bool
MP3InputStream::detect (const string& filename)
{
struct ScopedMHandle
{
mpg123_handle *mh = nullptr;
bool need_close = false;
~ScopedMHandle()
{
if (mh && need_close)
mpg123_close (mh);
if (mh)
mpg123_delete (mh);
}
};
int err = 0;
mp3_init();
mpg123_handle *mh = mpg123_new (NULL, &err);
if (err != MPG123_OK)
return "mpg123_new failed";
return false;
auto smh = ScopedMHandle { mh }; // cleanup on return
err = mpg123_param (mh, MPG123_ADD_FLAGS, MPG123_QUIET, 0);
if (err != MPG123_OK)
return "setting quiet mode failed";
// allow arbitary amount of data for resync */
err = mpg123_param (mh, MPG123_RESYNC_LIMIT, -1, 0);
if (err != MPG123_OK)
return "setting resync limit parameter failed";
// force floating point output
{
const long *rates;
size_t rate_count;
mpg123_format_none (mh);
mpg123_rates (&rates, &rate_count);
for (size_t i = 0; i < rate_count; i++)
{
err = mpg123_format (mh, rates[i], MPG123_MONO|MPG123_STEREO, MPG123_ENC_FLOAT_32);
if (err != MPG123_OK)
return mpg123_strerror (mh);
}
}
return false;
err = mpg123_open (mh, filename.c_str());
if (err != MPG123_OK)
return mpg123_strerror (mh);
return false;
smh.need_close = true;
long rate;
int channels;
int encoding;
err = mpg123_getformat (mh, &rate, &channels, &encoding);
if (err != MPG123_OK)
return mpg123_strerror (mh);
/* ensure that the format will not change */
mpg123_format_none (mh);
mpg123_format (mh, rate, channels, encoding);
return false;
size_t buffer_bytes = mpg123_outblock (mh);
assert (buffer_bytes % sizeof (float) == 0);
vector<float> buffer (buffer_bytes / sizeof (float));
vector<float> samples;
unsigned char buffer[buffer_bytes];
while (true)
for (size_t i = 0; i < 30; i++)
{
size_t done = 0;
err = mpg123_read (mh, reinterpret_cast<unsigned char *> (&buffer[0]), buffer_bytes, &done);
if (err == MPG123_OK)
{
const size_t n_values = done / sizeof (float);
samples.insert (samples.end(), buffer.begin(), buffer.begin() + n_values);
}
else if (err == MPG123_DONE)
{
wav_data = WavData (samples, channels, rate, 24);
return ""; /* success */
}
else if (err == MPG123_NEED_MORE)
size_t done;
err = mpg123_read (mh, buffer, buffer_bytes, &done);
if (err == MPG123_DONE)
{
// some mp3s have this error before reaching eof -> harmless
return true;
}
else
else if (err != MPG123_OK)
{
return mpg123_strerror (mh);
return false;
}
}
return true;
}
#ifndef AUDIOWMARK_MP3_INPUT_STREAM_HH
#define AUDIOWMARK_MP3_INPUT_STREAM_HH
#include <string>
#include <mpg123.h>
#include "audiostream.hh"
class MP3InputStream : public AudioInputStream
{
enum class State {
NEW,
OPEN,
CLOSED
};
int m_n_values = 0;
int m_n_channels = 0;
int m_sample_rate = 0;
size_t m_frames_left = 0;
bool m_need_close = false;
bool m_eof = false;
State m_state = State::NEW;
mpg123_handle *m_handle = nullptr;
std::vector<float> m_read_buffer;
public:
~MP3InputStream();
Error open (const std::string& filename);
Error read_frames (std::vector<float>& samples, size_t count);
void close();
int bit_depth() const override;
int sample_rate() const override;
int n_channels() const override;
size_t n_frames() const override;
static bool detect (const std::string& filename);
};
#endif /* AUDIOWMARK_MP3_INPUT_STREAM_HH */
......@@ -20,7 +20,7 @@ gcrypt_init()
/* version check: start libgcrypt initialization */
if (!gcry_check_version (GCRYPT_VERSION))
{
fprintf (stderr, "audiowmark: libgcrypt version mismatch\n");
error ("audiowmark: libgcrypt version mismatch\n");
exit (1);
}
......@@ -78,57 +78,50 @@ print (const string& label, const vector<unsigned char>& data)
}
#endif
Random::Random (uint64_t seed, Stream stream)
Random::Random (uint64_t start_seed, Stream stream)
{
gcrypt_init();
vector<unsigned char> ctr = get_start_counter (seed, stream);
// print ("CTR", ctr);
gcry_error_t gcry_ret = gcry_cipher_open (&aes_ctr_cipher, GCRY_CIPHER, GCRY_CIPHER_MODE_CTR, 0);
die_on_error ("gcry_cipher_open", gcry_ret);
gcry_ret = gcry_cipher_setkey (aes_ctr_cipher, &aes_key[0], aes_key.size());
die_on_error ("gcry_cipher_setkey", gcry_ret);
gcry_ret = gcry_cipher_setctr (aes_ctr_cipher, &ctr[0], ctr.size());
die_on_error ("gcry_cipher_setctr", gcry_ret);
}
gcry_ret = gcry_cipher_open (&seed_cipher, GCRY_CIPHER, GCRY_CIPHER_MODE_ECB, 0);
die_on_error ("gcry_cipher_open", gcry_ret);
Random::~Random()
{
gcry_cipher_close (aes_ctr_cipher);
gcry_ret = gcry_cipher_setkey (seed_cipher, &aes_key[0], aes_key.size());
die_on_error ("gcry_cipher_setkey", gcry_ret);
seed (start_seed, stream);
}
vector<unsigned char>
Random::get_start_counter (uint64_t seed, Stream stream)
void
Random::seed (uint64_t seed, Stream stream)
{
gcry_error_t gcry_ret;
gcry_cipher_hd_t cipher_hd;
gcry_ret = gcry_cipher_open (&cipher_hd, GCRY_CIPHER, GCRY_CIPHER_MODE_ECB, 0);
die_on_error ("gcry_cipher_open", gcry_ret);
gcry_ret = gcry_cipher_setkey (cipher_hd, &aes_key[0], aes_key.size());
die_on_error ("gcry_cipher_setkey", gcry_ret);
buffer_pos = 0;
buffer.clear();
vector<unsigned char> cipher_text (16);
vector<unsigned char> plain_text (16);
unsigned char plain_text[aes_key.size()] = { 0, };
unsigned char cipher_text[aes_key.size()];
uint64_to_buffer (seed, &plain_text[0]);
plain_text[8] = uint8_t (stream);
// print ("SEED", plain_text);
gcry_ret = gcry_cipher_encrypt (cipher_hd, &cipher_text[0], cipher_text.size(),
&plain_text[0], plain_text.size());
gcry_error_t gcry_ret = gcry_cipher_encrypt (seed_cipher, &cipher_text[0], aes_key.size(),
&plain_text[0], aes_key.size());
die_on_error ("gcry_cipher_encrypt", gcry_ret);
gcry_cipher_close (cipher_hd);
gcry_ret = gcry_cipher_setctr (aes_ctr_cipher, &cipher_text[0], aes_key.size());
die_on_error ("gcry_cipher_setctr", gcry_ret);
}
return cipher_text;
Random::~Random()
{
gcry_cipher_close (aes_ctr_cipher);
gcry_cipher_close (seed_cipher);
}
void
......@@ -151,11 +144,11 @@ Random::refill_buffer()
}
void
Random::die_on_error (const char *func, gcry_error_t error)
Random::die_on_error (const char *func, gcry_error_t err)
{
if (error)
if (err)
{
fprintf (stderr, "%s failed: %s/%s\n", func, gcry_strsource (error), gcry_strerror (error));
error ("%s failed: %s/%s\n", func, gcry_strsource (err), gcry_strerror (err));
exit (1); /* can't recover here */
}
......@@ -173,7 +166,7 @@ Random::load_global_key (const string& key_file)
FILE *f = fopen (key_file.c_str(), "r");
if (!f)
{
fprintf (stderr, "audiowmark: error opening key file: '%s'\n", key_file.c_str());
error ("audiowmark: error opening key file: '%s'\n", key_file.c_str());
exit (1);
}
......@@ -198,7 +191,7 @@ Random::load_global_key (const string& key_file)
vector<unsigned char> key = hex_str_to_vec (match[1].str());
if (key.size() != aes_key.size())
{
fprintf (stderr, "audiowmark: wrong key length in key file '%s', line %d\n => required key length is %zd bits\n", key_file.c_str(), line, aes_key.size() * 8);
error ("audiowmark: wrong key length in key file '%s', line %d\n => required key length is %zd bits\n", key_file.c_str(), line, aes_key.size() * 8);
exit (1);
}
aes_key = key;
......@@ -206,7 +199,7 @@ Random::load_global_key (const string& key_file)
}
else
{
fprintf (stderr, "audiowmark: parse error in key file '%s', line %d\n", key_file.c_str(), line);
error ("audiowmark: parse error in key file '%s', line %d\n", key_file.c_str(), line);
exit (1);
}
line++;
......@@ -215,12 +208,12 @@ Random::load_global_key (const string& key_file)
if (keys > 1)
{
fprintf (stderr, "audiowmark: key file '%s' contains more than one key\n", key_file.c_str());
error ("audiowmark: key file '%s' contains more than one key\n", key_file.c_str());
exit (1);
}
if (keys == 0)
{
fprintf (stderr, "audiowmark: key file '%s' contains no key\n", key_file.c_str());
error ("audiowmark: key file '%s' contains no key\n", key_file.c_str());
exit (1);
}
}
......
......@@ -19,12 +19,11 @@ public:
frame_position = 6
};
private:
gcry_cipher_hd_t aes_ctr_cipher;
gcry_cipher_hd_t aes_ctr_cipher = nullptr;
gcry_cipher_hd_t seed_cipher = nullptr;
std::vector<uint64_t> buffer;
size_t buffer_pos = 0;
std::vector<unsigned char> get_start_counter (uint64_t seed, Stream stream);
void die_on_error (const char *func, gcry_error_t error);
public:
Random (uint64_t seed, Stream stream);
......@@ -39,6 +38,7 @@ public:
return buffer[buffer_pos++];
}
void refill_buffer();
void seed (uint64_t seed, Stream stream);
template<class T> void
shuffle (std::vector<T>& result)
......
#include "rawconverter.hh"
#include <math.h>
using std::vector;
template<int BIT_DEPTH, RawFormat::Endian ENDIAN, RawFormat::Encoding ENCODING>
class RawConverterImpl : public RawConverter
{
public:
void to_raw (const std::vector<float>& samples, std::vector<unsigned char>& bytes);
void from_raw (const std::vector<unsigned char>& bytes, std::vector<float>& samples);
};
template<int BIT_DEPTH, RawFormat::Endian ENDIAN>
static RawConverter *
create_with_bits_endian (const RawFormat& raw_format, Error& error)
{
switch (raw_format.encoding())
{
case RawFormat::SIGNED: return new RawConverterImpl<BIT_DEPTH, ENDIAN, RawFormat::SIGNED>();
case RawFormat::UNSIGNED: return new RawConverterImpl<BIT_DEPTH, ENDIAN, RawFormat::UNSIGNED>();
}
error = Error ("unsupported encoding");
return nullptr;
}
template<int BIT_DEPTH>
static RawConverter *
create_with_bits (const RawFormat& raw_format, Error& error)
{
switch (raw_format.endian())
{
case RawFormat::LITTLE: return create_with_bits_endian <BIT_DEPTH, RawFormat::LITTLE> (raw_format, error);
case RawFormat::BIG: return create_with_bits_endian <BIT_DEPTH, RawFormat::BIG> (raw_format, error);
}
error = Error ("unsupported endianness");
return nullptr;
}
RawConverter *
RawConverter::create (const RawFormat& raw_format, Error& error)
{
error = Error::Code::NONE;
switch (raw_format.bit_depth())
{
case 16: return create_with_bits<16> (raw_format, error);
case 24: return create_with_bits<24> (raw_format, error);
default: error = Error ("unsupported bit depth");
return nullptr;
}
}
template<int BIT_DEPTH, RawFormat::Endian ENDIAN>
constexpr std::array<int, 3>
make_endian_shift ()
{
if (BIT_DEPTH == 16)
{
if (ENDIAN == RawFormat::Endian::LITTLE)
return { 16, 24, -1 };
else
return { 24, 16, -1 };
}
if (BIT_DEPTH == 24)
{
if (ENDIAN == RawFormat::Endian::LITTLE)
return { 8, 16, 24 };
else
return { 24, 16, 8 };
}
}
template<int BIT_DEPTH, RawFormat::Endian ENDIAN, RawFormat::Encoding ENCODING>
void
RawConverterImpl<BIT_DEPTH, ENDIAN, ENCODING>::to_raw (const vector<float>& samples, vector<unsigned char>& output_bytes)
{
constexpr int sample_width = BIT_DEPTH / 8;
constexpr auto eshift = make_endian_shift<BIT_DEPTH, ENDIAN>();
constexpr unsigned char sign_flip = ENCODING == RawFormat::SIGNED ? 0x00 : 0x80;
output_bytes.resize (sample_width * samples.size());
unsigned char *ptr = output_bytes.data();
for (size_t i = 0; i < samples.size(); i++)
{
const double norm = 0x80000000LL;
const double min_value = -0x80000000LL;
const double max_value = 0x7FFFFFFF;
const int sample = lrint (bound<double> (min_value, samples[i] * norm, max_value));
if (eshift[0] >= 0)
ptr[0] = (sample >> eshift[0]) ^ sign_flip;
if (eshift[1] >= 0)
ptr[1] = sample >> eshift[1];
if (eshift[2] >= 0)
ptr[2] = sample >> eshift[2];
ptr += sample_width;
}
}
template<int BIT_DEPTH, RawFormat::Endian ENDIAN, RawFormat::Encoding ENCODING>
void
RawConverterImpl<BIT_DEPTH, ENDIAN, ENCODING>::from_raw (const vector<unsigned char>& input_bytes, vector<float>& samples)
{
const unsigned char *ptr = input_bytes.data();
constexpr int sample_width = BIT_DEPTH / 8;
constexpr auto eshift = make_endian_shift<BIT_DEPTH, ENDIAN>();
constexpr unsigned char sign_flip = ENCODING == RawFormat::SIGNED ? 0x00 : 0x80;
samples.resize (input_bytes.size() / sample_width);
const double norm = 1.0 / 0x80000000LL;
for (size_t i = 0; i < samples.size(); i++)
{
int s32 = 0;
if (eshift[0] >= 0)
s32 += (ptr[0] ^ sign_flip) << eshift[0];
if (eshift[1] >= 0)
s32 += ptr[1] << eshift[1];
if (eshift[2] >= 0)
s32 += ptr[2] << eshift[2];
samples[i] = s32 * norm;
ptr += sample_width;
}
}
#ifndef AUDIOWMARK_RAW_CONVERTER_HH
#define AUDIOWMARK_RAW_CONVERTER_HH
#include "rawinputstream.hh"
class RawConverter
{
public:
static RawConverter *create (const RawFormat& raw_format, Error& error);
virtual void to_raw (const std::vector<float>& samples, std::vector<unsigned char>& bytes) = 0;
virtual void from_raw (const std::vector<unsigned char>& bytes, std::vector<float>& samples) = 0;
};
#endif /* AUDIOWMARK_RAW_CONVERTER_HH */
#include "rawinputstream.hh"
#include "rawconverter.hh"
#include <assert.h>
#include <string.h>
using std::string;
using std::vector;
RawFormat::RawFormat()
{
}
RawFormat::RawFormat (int n_channels, int sample_rate, int bit_depth) :
m_n_channels (n_channels),
m_sample_rate (sample_rate),
m_bit_depth (bit_depth)
{
}
void
RawFormat::set_channels (int channels)
{
m_n_channels = channels;
}
void
RawFormat::set_sample_rate (int rate)
{
m_sample_rate = rate;
}
void
RawFormat::set_bit_depth (int bits)
{
m_bit_depth = bits;
}
void
RawFormat::set_endian (Endian endian)
{
m_endian = endian;
}
void
RawFormat::set_encoding (Encoding encoding)
{
m_encoding = encoding;
}
RawInputStream::~RawInputStream()
{
close();
}
Error
RawInputStream::open (const string& filename, const RawFormat& format)
{
assert (m_state == State::NEW);
if (!format.n_channels())
return Error ("RawInputStream: input format: missing number of channels");
if (!format.bit_depth())
return Error ("RawInputStream: input format: missing bit depth");
if (!format.sample_rate())
return Error ("RawInputStream: input format: missing sample rate");
Error err = Error::Code::NONE;
m_raw_converter.reset (RawConverter::create (format, err));
if (err)
return err;
if (filename == "-")
{
m_input_file = stdin;
m_close_file = false;
}
else
{
m_input_file = fopen (filename.c_str(), "r");
if (!m_input_file)
return Error (strerror (errno));
m_close_file = true;
}
m_format = format;
m_state = State::OPEN;
return Error::Code::NONE;
}
int
RawInputStream::sample_rate() const
{
return m_format.sample_rate();
}
int
RawInputStream::bit_depth() const
{
return m_format.bit_depth();
}
size_t
RawInputStream::n_frames() const
{
return N_FRAMES_UNKNOWN;
}
int
RawInputStream::n_channels() const
{
return m_format.n_channels();
}
Error
RawInputStream::read_frames (vector<float>& samples, size_t count)
{
assert (m_state == State::OPEN);
const int n_channels = m_format.n_channels();
const int sample_width = m_format.bit_depth() / 8;
vector<unsigned char> input_bytes (count * n_channels * sample_width);
size_t r_count = fread (input_bytes.data(), n_channels * sample_width, count, m_input_file);
if (ferror (m_input_file))
return Error ("error reading sample data");
input_bytes.resize (r_count * n_channels * sample_width);
m_raw_converter->from_raw (input_bytes, samples);
return Error::Code::NONE;
}
void
RawInputStream::close()
{
if (m_state == State::OPEN)
{
if (m_close_file && m_input_file)
{
fclose (m_input_file);
m_input_file = nullptr;
m_close_file = false;
}
m_state = State::CLOSED;
}
}
#ifndef AUDIOWMARK_RAW_INPUT_STREAM_HH
#define AUDIOWMARK_RAW_INPUT_STREAM_HH
#include <string>
#include <memory>
#include <sndfile.h>
#include "audiostream.hh"
class RawFormat
{
public:
enum Endian {
LITTLE,
BIG
};
enum Encoding {
SIGNED,
UNSIGNED
};
private:
int m_n_channels = 2;
int m_sample_rate = 0;
int m_bit_depth = 16;
Endian m_endian = LITTLE;
Encoding m_encoding = SIGNED;
public:
RawFormat();
RawFormat (int n_channels, int sample_rate, int bit_depth);
int n_channels() const { return m_n_channels; }
int sample_rate() const { return m_sample_rate; }
int bit_depth() const { return m_bit_depth; }
Endian endian() const { return m_endian; }
Encoding encoding() const { return m_encoding; }
void set_channels (int channels);
void set_sample_rate (int rate);
void set_bit_depth (int bits);
void set_endian (Endian endian);
void set_encoding (Encoding encoding);
};
class RawConverter;
class RawInputStream : public AudioInputStream
{
enum class State {
NEW,
OPEN,
CLOSED
};
State m_state = State::NEW;
RawFormat m_format;
FILE *m_input_file = nullptr;
bool m_close_file = false;
std::unique_ptr<RawConverter> m_raw_converter;
public:
~RawInputStream();
Error open (const std::string& filename, const RawFormat& format);
Error read_frames (std::vector<float>& samples, size_t count) override;
void close();
int bit_depth() const override;
int sample_rate() const override;
size_t n_frames() const override;
int n_channels() const override;
};
#endif /* AUDIOWMARK_RAW_INPUT_STREAM_HH */
#include "rawoutputstream.hh"
#include <assert.h>
#include <string.h>
using std::string;
using std::vector;
RawOutputStream::~RawOutputStream()
{
close();
}
Error
RawOutputStream::open (const string& filename, const RawFormat& format)
{
assert (m_state == State::NEW);
if (!format.n_channels())
return Error ("RawOutputStream: output format: missing number of channels");
if (!format.bit_depth())
return Error ("RawOutputStream: output format: missing bit depth");
if (!format.sample_rate())
return Error ("RawOutputStream: output format: missing sample rate");
Error err = Error::Code::NONE;
m_raw_converter.reset (RawConverter::create (format, err));
if (err)
return err;
if (filename == "-")
{
m_output_file = stdout;
m_close_file = false;
}
else
{
m_output_file = fopen (filename.c_str(), "w");
if (!m_output_file)
return Error (strerror (errno));
m_close_file = true;
}
m_format = format;
m_state = State::OPEN;
return Error::Code::NONE;
}
int
RawOutputStream::sample_rate() const
{
return m_format.sample_rate();
}
int
RawOutputStream::bit_depth() const
{
return m_format.bit_depth();
}
int
RawOutputStream::n_channels() const
{
return m_format.n_channels();
}
Error
RawOutputStream::write_frames (const vector<float>& samples)
{
assert (m_state == State::OPEN);
vector<unsigned char> bytes;
m_raw_converter->to_raw (samples, bytes);
fwrite (&bytes[0], 1, bytes.size(), m_output_file);
if (ferror (m_output_file))
return Error ("write sample data failed");
return Error::Code::NONE;
}
Error
RawOutputStream::close()
{
if (m_state == State::OPEN)
{
if (m_output_file)
{
fflush (m_output_file);
if (ferror (m_output_file))
return Error ("error during flush");
}
if (m_close_file && m_output_file)
{
if (fclose (m_output_file) != 0)
return Error ("error during close");
m_output_file = nullptr;
m_close_file = false;
}
m_state = State::CLOSED;
}
return Error::Code::NONE;
}
#ifndef AUDIOWMARK_RAW_OUTPUT_STREAM_HH
#define AUDIOWMARK_RAW_OUTPUT_STREAM_HH
#include "rawinputstream.hh"
#include "rawconverter.hh"
#include <memory>
class RawOutputStream : public AudioOutputStream
{
enum class State {
NEW,
OPEN,
CLOSED
};
State m_state = State::NEW;
RawFormat m_format;
FILE *m_output_file = nullptr;
bool m_close_file = false;
std::unique_ptr<RawConverter> m_raw_converter;
public:
~RawOutputStream();
int bit_depth() const override;
int sample_rate() const override;
int n_channels() const override;
Error open (const std::string& filename, const RawFormat& format);
Error write_frames (const std::vector<float>& frames) override;
Error close();
};
#endif /* AUDIOWMARK_RAW_OUTPUT_STREAM_HH */
#include "sfinputstream.hh"
#include <assert.h>
using std::string;
using std::vector;
SFInputStream::~SFInputStream()
{
close();
}
Error
SFInputStream::open (const string& filename)
{
assert (m_state == State::NEW);
SF_INFO sfinfo = { 0, };
m_sndfile = sf_open (filename.c_str(), SFM_READ, &sfinfo);
int error = sf_error (m_sndfile);
if (error)
{
Error err (sf_strerror (m_sndfile));
if (m_sndfile)
{
m_sndfile = nullptr;
sf_close (m_sndfile);
}
return err;
}
m_n_channels = sfinfo.channels;
m_n_values = sfinfo.frames * sfinfo.channels;
m_sample_rate = sfinfo.samplerate;
switch (sfinfo.format & SF_FORMAT_SUBMASK)
{
case SF_FORMAT_PCM_U8:
case SF_FORMAT_PCM_S8:
m_bit_depth = 8;
break;
case SF_FORMAT_PCM_16:
m_bit_depth = 16;
break;
case SF_FORMAT_PCM_24:
m_bit_depth = 24;
break;
case SF_FORMAT_FLOAT:
case SF_FORMAT_PCM_32:
m_bit_depth = 32;
break;
case SF_FORMAT_DOUBLE:
m_bit_depth = 64;
break;
default:
m_bit_depth = 32; /* unknown */
}
m_state = State::OPEN;
return Error::Code::NONE;
}
int
SFInputStream::sample_rate() const
{
return m_sample_rate;
}
int
SFInputStream::bit_depth() const
{
return m_bit_depth;
}
Error
SFInputStream::read_frames (vector<float>& samples, size_t count)
{
assert (m_state == State::OPEN);
vector<int> isamples (count * m_n_channels);
sf_count_t r_count = sf_readf_int (m_sndfile, &isamples[0], count);
if (sf_error (m_sndfile))
return Error (sf_strerror (m_sndfile));
/* reading a wav file and saving it again with the libsndfile float API will
* change some values due to normalization issues:
* http://www.mega-nerd.com/libsndfile/FAQ.html#Q010
*
* to avoid the problem, we use the int API and do the conversion beween int
* and float manually - the important part is that the normalization factors
* used during read and write are identical
*/
samples.resize (r_count * m_n_channels);
const double norm = 1.0 / 0x80000000LL;
for (size_t i = 0; i < samples.size(); i++)
samples[i] = isamples[i] * norm;
return Error::Code::NONE;
}
void
SFInputStream::close()
{
if (m_state == State::OPEN)
{
assert (m_sndfile);
sf_close (m_sndfile);
m_sndfile = nullptr;
m_state = State::CLOSED;
}
}
#ifndef AUDIOWMARK_SF_INPUT_STREAM_HH
#define AUDIOWMARK_SF_INPUT_STREAM_HH
#include <string>
#include <sndfile.h>
#include "audiostream.hh"
class SFInputStream : public AudioInputStream
{
SNDFILE *m_sndfile = nullptr;
int m_n_channels = 0;
int m_n_values = 0;
int m_bit_depth = 0;
int m_sample_rate = 0;
enum class State {
NEW,
OPEN,
CLOSED
};
State m_state = State::NEW;
public:
~SFInputStream();
Error open (const std::string& filename);
Error read_frames (std::vector<float>& samples, size_t count);
void close();
int
n_channels() const
{
return m_n_channels;
}
int sample_rate() const override;
int bit_depth() const override;
size_t
n_values() const
{
return m_n_values;
}
size_t
n_frames() const override
{
return m_n_values / m_n_channels;
}
};
#endif /* AUDIOWMARK_SF_INPUT_STREAM_HH */
#include "sfoutputstream.hh"
#include "utils.hh"
#include <math.h>
#include <assert.h>
using std::string;
using std::vector;
SFOutputStream::~SFOutputStream()
{
close();
}
Error
SFOutputStream::open (const string& filename, int n_channels, int sample_rate, int bit_depth, size_t n_frames)
{
assert (m_state == State::NEW);
m_sample_rate = sample_rate;
m_n_channels = n_channels;
SF_INFO sfinfo = {0,};
sfinfo.samplerate = sample_rate;
sfinfo.channels = n_channels;
if (bit_depth > 16)
{
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
m_bit_depth = 24;
}
else
{
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
m_bit_depth = 16;
}
m_sndfile = sf_open (filename.c_str(), SFM_WRITE, &sfinfo);
int error = sf_error (m_sndfile);
if (error)
{
string msg = sf_strerror (m_sndfile);
if (m_sndfile)
sf_close (m_sndfile);
return Error (msg);
}
m_state = State::OPEN;
return Error::Code::NONE;
}
Error
SFOutputStream::close()
{
if (m_state == State::OPEN)
{
assert (m_sndfile);
if (sf_close (m_sndfile))
return Error ("sf_close returned an error");
m_state = State::CLOSED;
}
return Error::Code::NONE;
}
Error
SFOutputStream::write_frames (const vector<float>& samples)
{
vector<int> isamples (samples.size());
for (size_t i = 0; i < samples.size(); i++)
{
const double norm = 0x80000000LL;
const double min_value = -0x80000000LL;
const double max_value = 0x7FFFFFFF;
isamples[i] = lrint (bound<double> (min_value, samples[i] * norm, max_value));
}
sf_count_t frames = samples.size() / m_n_channels;
sf_count_t count = sf_writef_int (m_sndfile, &isamples[0], frames);
if (sf_error (m_sndfile))
return Error (sf_strerror (m_sndfile));
if (count != frames)
return Error ("writing sample data failed: short write");
return Error::Code::NONE;
}
int
SFOutputStream::bit_depth() const
{
return m_bit_depth;
}
int
SFOutputStream::sample_rate() const
{
return m_sample_rate;
}
int
SFOutputStream::n_channels() const
{
return m_n_channels;
}
#ifndef AUDIOWMARK_SF_OUTPUT_STREAM_HH
#define AUDIOWMARK_SF_OUTPUT_STREAM_HH
#include <string>
#include <sndfile.h>
#include "audiostream.hh"
class SFOutputStream : public AudioOutputStream
{
SNDFILE *m_sndfile = nullptr;
int m_bit_depth = 0;
int m_sample_rate = 0;
int m_n_channels = 0;
enum class State {
NEW,
OPEN,
CLOSED
};
State m_state = State::NEW;
public:
~SFOutputStream();
Error open (const std::string& filename, int n_channels, int sample_rate, int bit_depth, size_t n_frames);
Error write_frames (const std::vector<float>& frames) override;
Error close() override;
int bit_depth() const override;
int sample_rate() const override;
int n_channels() const override;
};
#endif /* AUDIOWMARK_SF_OUTPUT_STREAM_HH */
#include "stdoutwavoutputstream.hh"
#include "utils.hh"
#include <assert.h>
#include <math.h>
using std::string;
using std::vector;
StdoutWavOutputStream::~StdoutWavOutputStream()
{
close();
}
int
StdoutWavOutputStream::sample_rate() const
{
return m_sample_rate;
}
int
StdoutWavOutputStream::bit_depth() const
{
return m_bit_depth;
}
int
StdoutWavOutputStream::n_channels() const
{
return m_n_channels;
}
static void
header_append_str (vector<unsigned char>& bytes, const string& str)
{
for (auto ch : str)
bytes.push_back (ch);
}
static void
header_append_u32 (vector<unsigned char>& bytes, uint32_t u)
{
bytes.push_back (u);
bytes.push_back (u >> 8);
bytes.push_back (u >> 16);
bytes.push_back (u >> 24);
}
static void
header_append_u16 (vector<unsigned char>& bytes, uint16_t u)
{
bytes.push_back (u);
bytes.push_back (u >> 8);
}
Error
StdoutWavOutputStream::open (int n_channels, int sample_rate, int bit_depth, size_t n_frames)
{
assert (m_state == State::NEW);
if (bit_depth != 16 && bit_depth != 24)
{
return Error ("StdoutWavOutputStream::open: unsupported bit depth");
}
if (n_frames == AudioInputStream::N_FRAMES_UNKNOWN)
{
return Error ("unable to write wav format to standard out without input length information");
}
RawFormat format;
format.set_bit_depth (bit_depth);
Error err = Error::Code::NONE;
m_raw_converter.reset (RawConverter::create (format, err));
if (err)
return err;
vector<unsigned char> header_bytes;
size_t data_size = n_frames * n_channels * ((bit_depth + 7) / 8);
m_close_padding = data_size & 1; // padding to ensure even data size
size_t aligned_data_size = data_size + m_close_padding;
header_append_str (header_bytes, "RIFF");
header_append_u32 (header_bytes, 36 + aligned_data_size);
header_append_str (header_bytes, "WAVE");
// subchunk 1
header_append_str (header_bytes, "fmt ");
header_append_u32 (header_bytes, 16); // subchunk size
header_append_u16 (header_bytes, 1); // uncompressed audio
header_append_u16 (header_bytes, n_channels);
header_append_u32 (header_bytes, sample_rate);
header_append_u32 (header_bytes, sample_rate * n_channels * bit_depth / 8); // byte rate
header_append_u16 (header_bytes, n_channels * bit_depth / 8); // block align
header_append_u16 (header_bytes, bit_depth); // bits per sample
// subchunk 2
header_append_str (header_bytes, "data");
header_append_u32 (header_bytes, data_size);
fwrite (&header_bytes[0], 1, header_bytes.size(), stdout);
if (ferror (stdout))
return Error ("write wav header failed");
m_bit_depth = bit_depth;
m_sample_rate = sample_rate;
m_n_channels = n_channels;
m_state = State::OPEN;
return Error::Code::NONE;
}
Error
StdoutWavOutputStream::write_frames (const vector<float>& samples)
{
vector<unsigned char> output_bytes;
m_raw_converter->to_raw (samples, output_bytes);
fwrite (&output_bytes[0], 1, output_bytes.size(), stdout);
if (ferror (stdout))
return Error ("write sample data failed");
return Error::Code::NONE;
}
Error
StdoutWavOutputStream::close()
{
if (m_state == State::OPEN)
{
for (size_t i = 0; i < m_close_padding; i++)
{
fputc (0, stdout);
if (ferror (stdout))
return Error ("write wav padding failed");
}
fflush (stdout);
if (ferror (stdout))
return Error ("error during flush");
m_state = State::CLOSED;
}
return Error::Code::NONE;
}
#ifndef AUDIOWMARK_STDOUT_WAV_STREAM_HH
#define AUDIOWMARK_STDOUT_WAV_STREAM_HH
#include "audiostream.hh"
#include "rawconverter.hh"
#include <string>
class StdoutWavOutputStream : public AudioOutputStream
{
int m_bit_depth = 0;
int m_sample_rate = 0;
int m_n_channels = 0;
size_t m_close_padding = 0;
enum class State {
NEW,
OPEN,
CLOSED
};
State m_state = State::NEW;
std::unique_ptr<RawConverter> m_raw_converter;
public:
~StdoutWavOutputStream();
Error open (int n_channels, int sample_rate, int bit_depth, size_t n_frames);
Error write_frames (const std::vector<float>& frames) override;
Error close();
int sample_rate() const override;
int bit_depth() const override;
int n_channels() const override;
};
#endif
#include "mp3.hh"
#include "mp3inputstream.hh"
#include "wavdata.hh"
using std::string;
......@@ -8,10 +9,18 @@ main (int argc, char **argv)
WavData wd;
if (argc >= 2)
{
if (mp3_detect (argv[1]))
if (MP3InputStream::detect (argv[1]))
{
string error = mp3_load (argv[1], wd);
if (error == "")
MP3InputStream m3i;
Error err = m3i.open (argv[1]);
if (err)
{
printf ("mp3 open %s failed: %s\n", argv[1], err.message());
return 1;
}
err = wd.load (&m3i);
if (!err)
{
int sec = wd.n_values() / wd.n_channels() / wd.sample_rate();
......@@ -24,7 +33,7 @@ main (int argc, char **argv)
}
else
{
printf ("mp3 load %s failed: %s\n", argv[1], error.c_str());
printf ("mp3 load %s failed: %s\n", argv[1], err.message());
return 1;
}
}
......
#include <string>
#include <vector>
#include <sndfile.h>
#include <assert.h>
#include <math.h>
#include "sfinputstream.hh"
#include "stdoutwavoutputstream.hh"
#include "utils.hh"
using std::string;
using std::vector;
int
main (int argc, char **argv)
{
SFInputStream in;
StdoutWavOutputStream out;
std::string filename = (argc >= 2) ? argv[1] : "-";
Error err = in.open (filename.c_str());
if (err)
{
fprintf (stderr, "teststream: open input failed: %s\n", err.message());
return 1;
}
err = out.open (in.n_channels(), in.sample_rate(), 16, in.n_frames());
if (err)
{
fprintf (stderr, "teststream: open output failed: %s\n", err.message());
return 1;
}
vector<float> samples;
do
{
in.read_frames (samples, 1024);
out.write_frames (samples);
}
while (samples.size());
}
#!/usr/bin/env python3
# test how long the watermarker takes until the first audio sample is available
import subprocess
import shlex
import time
import sys
seconds = 0
for i in range (10):
start_time = time.time() * 1000
proc = subprocess.Popen (shlex.split (sys.argv[1]), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# we wait for actual audio data, so we read somewhat larger amount of data that the wave header
x = proc.stdout.read (1000)
end_time = time.time() * 1000
seconds += end_time - start_time
print ("%.2f" % (end_time - start_time), x[0:4], len (x))
print ("%.2f" % (seconds / 10), "avg")
#include "utils.hh"
#include "stdarg.h"
using std::vector;
using std::string;
......@@ -89,3 +90,66 @@ vec_to_hex_str (const vector<unsigned char>& vec)
}
return s;
}
static Log log_level = Log::INFO;
void
set_log_level (Log level)
{
log_level = level;
}
static void
logv (Log log, const char *format, va_list vargs)
{
if (log >= log_level)
{
char buffer[1024];
vsnprintf (buffer, sizeof (buffer), format, vargs);
/* could support custom log function here */
fprintf (stderr, "%s", buffer);
fflush (stderr);
}
}
void
error (const char *format, ...)
{
va_list ap;
va_start (ap, format);
logv (Log::ERROR, format, ap);
va_end (ap);
}
void
warning (const char *format, ...)
{
va_list ap;
va_start (ap, format);
logv (Log::WARNING, format, ap);
va_end (ap);
}
void
info (const char *format, ...)
{
va_list ap;
va_start (ap, format);
logv (Log::INFO, format, ap);
va_end (ap);
}
void
debug (const char *format, ...)
{
va_list ap;
va_start (ap, format);
logv (Log::DEBUG, format, ap);
va_end (ap);
}
......@@ -17,4 +17,73 @@ bound (const T& min_value, const T& value, const T& max_value)
return std::min (std::max (value, min_value), max_value);
}
// detect compiler
#if __clang__
#define AUDIOWMARK_COMP_CLANG
#elif __GNUC__ > 2
#define AUDIOWMARK_COMP_GCC
#else
#error "unsupported compiler"
#endif
#ifdef AUDIOWMARK_COMP_GCC
#define AUDIOWMARK_PRINTF(format_idx, arg_idx) __attribute__ ((__format__ (gnu_printf, format_idx, arg_idx)))
#else
#define AUDIOWMARK_PRINTF(format_idx, arg_idx) __attribute__ ((__format__ (__printf__, format_idx, arg_idx)))
#endif
void error (const char *format, ...) AUDIOWMARK_PRINTF (1, 2);
void warning (const char *format, ...) AUDIOWMARK_PRINTF (1, 2);
void info (const char *format, ...) AUDIOWMARK_PRINTF (1, 2);
void debug (const char *format, ...) AUDIOWMARK_PRINTF (1, 2);
enum class Log { ERROR = 3, WARNING = 2, INFO = 1, DEBUG = 0 };
void set_log_level (Log level);
class Error
{
public:
enum class Code {
NONE,
STR
};
Error (Code code = Code::NONE) :
m_code (code)
{
switch (code)
{
case Code::NONE:
m_message = "OK";
break;
default:
m_message = "Unknown error";
}
}
explicit
Error (const std::string& message) :
m_code (Code::STR),
m_message (message)
{
}
Code
code()
{
return m_code;
}
const char *
message()
{
return m_message.c_str();
}
operator bool()
{
return m_code != Code::NONE;
}
private:
Code m_code;
std::string m_message;
};
#endif /* AUDIOWMARK_UTILS_HH */
#include "wavdata.hh"
#include "mp3.hh"
#include "utils.hh"
#include "audiostream.hh"
#include "sfinputstream.hh"
#include "sfoutputstream.hh"
#include "mp3inputstream.hh"
#include <memory>
#include <math.h>
#include <sndfile.h>
using std::string;
using std::vector;
......@@ -20,174 +23,58 @@ WavData::WavData (const vector<float>& samples, int n_channels, int sample_rate,
m_bit_depth = bit_depth;
}
bool
Error
WavData::load (const string& filename)
{
SF_INFO sfinfo = { 0, };
Error err;
SNDFILE *sndfile = sf_open (filename.c_str(), SFM_READ, &sfinfo);
std::unique_ptr<AudioInputStream> in_stream = AudioInputStream::create (filename, err);
if (err)
return err;
int error = sf_error (sndfile);
if (error)
{
if (mp3_detect (filename))
{
string error = mp3_load (filename, *this);
if (error == "")
{
return true; // mp3 loaded successfully
}
else
{
m_error_blurb = "mp3 load error: " + error;
return false;
}
}
else
{
m_error_blurb = sf_strerror (sndfile);
if (sndfile)
sf_close (sndfile);
return false;
}
}
vector<int> isamples (sfinfo.frames * sfinfo.channels);
sf_count_t count = sf_readf_int (sndfile, &isamples[0], sfinfo.frames);
error = sf_error (sndfile);
if (error)
{
m_error_blurb = sf_strerror (sndfile);
sf_close (sndfile);
return false;
}
if (count != sfinfo.frames)
{
m_error_blurb = "reading sample data failed: short read";
sf_close (sndfile);
return false;
}
m_samples.resize (sfinfo.frames * sfinfo.channels);
/* reading a wav file and saving it again with the libsndfile float API will
* change some values due to normalization issues:
* http://www.mega-nerd.com/libsndfile/FAQ.html#Q010
*
* to avoid the problem, we use the int API and do the conversion beween int
* and float manually - the important part is that the normalization factors
* used during read and write are identical
*/
const double norm = 1.0 / 0x80000000LL;
for (size_t i = 0; i < m_samples.size(); i++)
m_samples[i] = isamples[i] * norm;
m_sample_rate = sfinfo.samplerate;
m_n_channels = sfinfo.channels;
return load (in_stream.get());
}
switch (sfinfo.format & SF_FORMAT_SUBMASK)
Error
WavData::load (AudioInputStream *in_stream)
{
vector<float> m_buffer;
while (true)
{
case SF_FORMAT_PCM_U8:
case SF_FORMAT_PCM_S8:
m_bit_depth = 8;
break;
case SF_FORMAT_PCM_16:
m_bit_depth = 16;
break;
case SF_FORMAT_PCM_24:
m_bit_depth = 24;
break;
case SF_FORMAT_FLOAT:
case SF_FORMAT_PCM_32:
m_bit_depth = 32;
break;
Error err = in_stream->read_frames (m_buffer, 1024);
if (err)
return err;
case SF_FORMAT_DOUBLE:
m_bit_depth = 64;
if (!m_buffer.size())
{
/* reached eof */
break;
default:
m_bit_depth = 32; /* unknown */
}
m_samples.insert (m_samples.end(), m_buffer.begin(), m_buffer.end());
}
m_sample_rate = in_stream->sample_rate();
m_n_channels = in_stream->n_channels();
m_bit_depth = in_stream->bit_depth();
error = sf_close (sndfile);
if (error)
{
m_error_blurb = sf_error_number (error);
return false;
}
return true;
return Error::Code::NONE;
}
bool
Error
WavData::save (const string& filename)
{
SF_INFO sfinfo = {0,};
sfinfo.samplerate = m_sample_rate;
sfinfo.channels = m_n_channels;
if (m_bit_depth > 16)
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
else
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
std::unique_ptr<AudioOutputStream> out_stream;
Error err;
SNDFILE *sndfile = sf_open (filename.c_str(), SFM_WRITE, &sfinfo);
int error = sf_error (sndfile);
if (error)
{
m_error_blurb = sf_strerror (sndfile);
if (sndfile)
sf_close (sndfile);
return false;
}
vector<int> isamples (m_samples.size());
for (size_t i = 0; i < m_samples.size(); i++)
{
const double norm = 0x80000000LL;
const double min_value = -0x80000000LL;
const double max_value = 0x7FFFFFFF;
isamples[i] = lrint (bound<double> (min_value, m_samples[i] * norm, max_value));
}
out_stream = AudioOutputStream::create (filename, m_n_channels, m_sample_rate, m_bit_depth, m_samples.size() / m_n_channels, err);
if (err)
return err;
sf_count_t frames = m_samples.size() / m_n_channels;
sf_count_t count = sf_writef_int (sndfile, &isamples[0], frames);
err = out_stream->write_frames (m_samples);
if (err)
return err;
error = sf_error (sndfile);
if (error)
{
m_error_blurb = sf_strerror (sndfile);
sf_close (sndfile);
return false;
}
if (count != frames)
{
m_error_blurb = "writing sample data failed: short write";
sf_close (sndfile);
return false;
}
error = sf_close (sndfile);
if (error)
{
m_error_blurb = sf_error_number (error);
return false;
}
return true;
err = out_stream->close();
return err;
}
int
......@@ -207,9 +94,3 @@ WavData::set_samples (const vector<float>& samples)
{
m_samples = samples;
}
const char *
WavData::error_blurb() const
{
return m_error_blurb.c_str();
}
......@@ -4,24 +4,26 @@
#include <string>
#include <vector>
#include "utils.hh"
#include "audiostream.hh"
class WavData
{
std::vector<float> m_samples;
int m_sample_rate = 0;
int m_n_channels = 0;
int m_bit_depth = 0;
std::string m_error_blurb;
public:
WavData();
WavData (const std::vector<float>& samples, int n_channels, int sample_rate, int bit_depth);
bool load (const std::string& filename);
bool save (const std::string& filename);
Error load (AudioInputStream *in_stream);
Error load (const std::string& filename);
Error save (const std::string& filename);
int sample_rate() const;
int bit_depth() const;
const char *error_blurb() const;
int
n_channels() const
......
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