Commit 70ee91d0 authored by Stefan Westerfeld's avatar Stefan Westerfeld

Implement input stream for mp3 files.

Signed-off-by: Stefan Westerfeld's avatarStefan Westerfeld <stefan@space.twc.de>
parent 1172bb19
......@@ -3,7 +3,7 @@ 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 \
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
rawconverter.cc rawconverter.hh mp3inputstream.cc mp3inputstream.hh
COMMON_LIBS = $(SNDFILE_LIBS) $(FFTW_LIBS) $(LIBGCRYPT_LIBS) $(LIBMPG123_LIBS)
audiowmark_SOURCES = audiowmark.cc fft.cc fft.hh $(COMMON_SRC)
......
......@@ -16,6 +16,8 @@
#include "stdoutwavoutputstream.hh"
#include "rawinputstream.hh"
#include "rawoutputstream.hh"
#include "mp3.hh"
#include "mp3inputstream.hh"
#include <zita-resampler/resampler.h>
#include <zita-resampler/vresampler.h>
......@@ -1216,7 +1218,19 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
SFInputStream *sistream = new SFInputStream();
in_stream.reset (sistream);
Error err = sistream->open (infile);
if (err)
if (err && mp3_detect (infile))
{
MP3InputStream *mistream = new MP3InputStream();
in_stream.reset (mistream);
err = mistream->open (infile);
if (err)
{
fprintf (stderr, "audiowmark: error opening mp3 %s: %s\n", infile.c_str(), err.message());
return 1;
}
}
else if (err)
{
fprintf (stderr, "audiowmark: error opening %s: %s\n", infile.c_str(), err.message());
return 1;
......@@ -1330,6 +1344,7 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
}
total_output_frames += samples.size() / n_channels;
}
fprintf (stderr, "total output: %zd, expected: %zd\n", total_output_frames, in_stream->n_frames());
#if 0
if (Params::snr)
{
......
......@@ -7,5 +7,6 @@
bool mp3_detect (const std::string& filename);
std::string mp3_load (const std::string& filename, WavData& wav_data);
void mp3_init();
#endif /* AUDIOWMARK_MP3_HH */
#include "mp3inputstream.hh"
#include "mp3.hh"
#include <mpg123.h>
#include <assert.h>
using std::min;
MP3InputStream::~MP3InputStream()
{
close();
}
Error
MP3InputStream::open (const std::string& filename)
{
int err = 0;
mp3_init();
m_handle = mpg123_new (nullptr, &err);
if (err != MPG123_OK)
return Error ("mpg123_new failed");
err = mpg123_param (m_handle, MPG123_ADD_FLAGS, MPG123_QUIET, 0);
if (err != MPG123_OK)
return Error ("setting quiet mode failed");
// allow arbitary amount of data for resync */
err = mpg123_param (m_handle, MPG123_RESYNC_LIMIT, -1, 0);
if (err != MPG123_OK)
return Error ("setting resync limit parameter failed");
// 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 Error (mpg123_strerror (m_handle));
m_need_close = true;
/* scan headers to get best possible length estimate */
mpg123_scan (m_handle);
long rate;
int channels;
int encoding;
err = mpg123_getformat (m_handle, &rate, &channels, &encoding);
if (err != MPG123_OK)
return Error (mpg123_strerror (m_handle));
/* 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;
return Error::Code::NONE;
}
Error
MP3InputStream::read_frames (std::vector<float>& samples, size_t count)
{
bool eof = false;
while (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;
int err = mpg123_read (m_handle, reinterpret_cast<unsigned char *> (&buffer[0]), buffer_bytes, &done);
if (err == MPG123_OK)
{
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_DONE)
{
eof = true;
break;
}
else if (err == MPG123_NEED_MORE)
{
// some mp3s have this error before reaching eof -> harmless
eof = true;
break;
}
else
{
return Error (mpg123_strerror (m_handle));
}
}
/* pad zero samples at end if necessary to match the number of frames we promised to deliver */
if (eof && m_read_buffer.size() < m_frames_left * m_n_channels)
{
fprintf (stderr, "mp3: padding %zd frames at end\n", m_frames_left - m_read_buffer.size() / 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)
{
fprintf (stderr, "mp3: ignoring read request: %zd frames at end\n", 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 -= samples.size() / m_n_channels;
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;
}
}
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;
}
#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;
int m_frames_left = 0;
bool m_need_close = 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;
};
#endif /* AUDIOWMARK_MP3_INPUT_STREAM_HH */
......@@ -114,6 +114,7 @@ SFInputStream::close()
assert (m_sndfile);
sf_close (m_sndfile);
m_sndfile = nullptr;
m_state = State::CLOSED;
}
}
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