commit da184788d11e6de9d1c316027c591d37ff2ce97e
parent a75d6408e8687bccdbc52d5d823b414d7b83f097
Author: Alexander Kojevnikov <alexander@kojevnikov.com>
Date: Tue, 19 Feb 2013 10:10:24 -0800
C++ify spek_audio
Diffstat:
14 files changed, 432 insertions(+), 407 deletions(-)
diff --git a/configure.ac b/configure.ac
@@ -5,8 +5,8 @@ AM_INIT_AUTOMAKE([1.11.1 foreign no-dist-gzip dist-xz])
AM_SILENT_RULES([yes])
AC_LANG([C++])
-AC_PROG_CXX([clang++ g++])
-CXXFLAGS="$CXXFLAGS -std=c++11"
+AC_PROG_CXX([g++47 g++])
+CXXFLAGS="$CXXFLAGS -std=gnu++0x -Wall -Wextra"
AC_PROG_CXXCPP
AC_PROG_RANLIB
AC_PROG_INSTALL
diff --git a/src/Makefile.am b/src/Makefile.am
@@ -14,18 +14,18 @@ libspek_a_SOURCES = \
libspek_a_CPPFLAGS = \
-include config.h \
- -pthread
+ -pthread \
+ $(WX_CPPFLAGS)
libspek_a_CXXFLAGS = \
- $(FFMPEG_CFLAGS)
+ $(FFMPEG_CFLAGS) \
+ $(WX_CXXFLAGS_ONLY)
bin_PROGRAMS = spek
spek_SOURCES = \
spek-artwork.cc \
spek-artwork.h \
- spek-audio-desc.cc \
- spek-audio-desc.h \
spek-events.cc \
spek-events.h \
spek-platform.cc \
diff --git a/src/spek-audio-desc.cc b/src/spek-audio-desc.cc
@@ -1,103 +0,0 @@
-/* spek-audio-desc.cc
- *
- * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com>
- *
- * Spek is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Spek is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Spek. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <wx/arrstr.h>
-#include <wx/intl.h>
-
-#include <spek-audio.h>
-
-#include "spek-audio-desc.h"
-
-#define ngettext wxPLURAL
-
-wxString spek_audio_desc(const struct spek_audio_properties *properties)
-{
- wxArrayString items;
-
- if (properties->codec_name) {
- items.Add(wxString::FromUTF8(properties->codec_name));
- }
- if (properties->bit_rate) {
- items.Add(wxString::Format(_("%d kbps"), (properties->bit_rate + 500) / 1000));
- }
- if (properties->sample_rate) {
- items.Add(wxString::Format(_("%d Hz"), properties->sample_rate));
- }
- // Include bits per sample only if there is no bitrate.
- if (properties->bits_per_sample && !properties->bit_rate) {
- items.Add(wxString::Format(
- ngettext("%d bit", "%d bits", properties->bits_per_sample),
- properties->bits_per_sample
- ));
- }
- if (properties->channels) {
- items.Add(wxString::Format(
- ngettext("%d channel", "%d channels", properties->channels),
- properties->channels
- ));
- }
-
- wxString desc;
- for (int i = 0; i < items.GetCount(); ++i) {
- if (i) {
- desc += wxT(", ");
- }
- desc += items[i];
- }
-
- if (properties->error) {
- wxString error;
- switch (properties->error) {
- case SPEK_AUDIO_CANNOT_OPEN_FILE:
- error = _("Cannot open input file");
- break;
- case SPEK_AUDIO_NO_STREAMS:
- error = _("Cannot find stream info");
- break;
- case SPEK_AUDIO_NO_AUDIO:
- error = _("The file contains no audio streams");
- break;
- case SPEK_AUDIO_NO_DECODER:
- error = _("Cannot find decoder");
- break;
- case SPEK_AUDIO_NO_DURATION:
- error = _("Unknown duration");
- break;
- case SPEK_AUDIO_NO_CHANNELS:
- error = _("No audio channels");
- break;
- case SPEK_AUDIO_CANNOT_OPEN_DECODER:
- error = _("Cannot open decoder");
- break;
- case SPEK_AUDIO_BAD_SAMPLE_FORMAT:
- error = _("Unsupported sample format");
- break;
- case SPEK_AUDIO_OK:
- break;
- }
-
- if (desc.IsEmpty()) {
- desc = error;
- } else {
- // TRANSLATORS: first %s is the error message, second %s is stream description.
- desc = wxString::Format(_("%s: %s"), error.c_str(), desc.c_str());
- }
- }
-
- return desc;
-}
diff --git a/src/spek-audio-desc.h b/src/spek-audio-desc.h
@@ -1,28 +0,0 @@
-/* spek-audio-desc.h
- *
- * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com>
- *
- * Spek is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Spek is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Spek. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef SPEK_AUDIO_DESC_H_
-#define SPEK_AUDIO_DESC_H_
-
-#include <wx/string.h>
-
-struct spek_audio_properties;
-
-wxString spek_audio_desc(const struct spek_audio_properties *properties);
-
-#endif
diff --git a/src/spek-audio.cc b/src/spek-audio.cc
@@ -16,10 +16,8 @@
* along with Spek. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <string.h>
-
-#define __STDC_CONSTANT_MACROS
extern "C" {
+#define __STDC_CONSTANT_MACROS
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
@@ -27,166 +25,253 @@ extern "C" {
#include "spek-audio.h"
-struct spek_audio_context
+class AudioFileImpl : public AudioFile
{
+public:
+ AudioFileImpl(
+ AudioError error, AVFormatContext *format_context, int audio_stream,
+ const std::string& codec_name, int bit_rate, int sample_rate, int bits_per_sample,
+ int channels, double duration, bool is_planar, int width, bool fp
+ );
+ ~AudioFileImpl() override;
+ void start(int samples) override;
+ int read() override;
+
+ AudioError get_error() const override { return this->error; }
+ std::string get_codec_name() const override { return this->codec_name; }
+ int get_bit_rate() const override { return this->bit_rate; }
+ int get_sample_rate() const override { return this->sample_rate; }
+ int get_bits_per_sample() const override { return this->bits_per_sample; }
+ int get_channels() const override { return this->channels; }
+ double get_duration() const override { return this->duration; }
+ int get_width() const override { return this->width; }
+ bool get_fp() const override { return this->fp; }
+ const uint8_t *get_buffer() const override { return this->buffer; }
+ int64_t get_frames_per_interval() const override { return this->frames_per_interval; }
+ int64_t get_error_per_interval() const override { return this->error_per_interval; }
+ int64_t get_error_base() const override { return this->error_base; }
+
+private:
+ AudioError error;
AVFormatContext *format_context;
int audio_stream;
- AVCodecContext *codec_context;
- AVStream *stream;
- AVCodec *codec;
- int buffer_size;
+ std::string codec_name;
+ int bit_rate;
+ int sample_rate;
+ int bits_per_sample;
+ int channels;
+ double duration;
+ bool is_planar;
+ int width;
+ bool fp;
+
AVPacket packet;
int offset;
- int is_planar;
AVFrame *frame;
-
- struct spek_audio_properties properties;
+ int buffer_size;
+ uint8_t *buffer;
+ // TODO: these guys don't belong here, move them somewhere else when revamping the pipeline
+ int64_t frames_per_interval;
+ int64_t error_per_interval;
+ int64_t error_base;
};
-void spek_audio_init()
+Audio::Audio()
{
av_register_all();
}
-const struct spek_audio_properties * spek_audio_get_properties(struct spek_audio_context *cx)
-{
- return &cx->properties;
-}
-
-struct spek_audio_context * spek_audio_open(const char *path)
+std::unique_ptr<AudioFile> Audio::open(const std::string& file_name)
{
- // TODO: malloc and initialise explicitely
- struct spek_audio_context *cx =
- (spek_audio_context *)calloc(1, sizeof(struct spek_audio_context));
+ AudioError error = AudioError::OK;
- if (avformat_open_input(&cx->format_context, path, NULL, NULL) != 0) {
- cx->properties.error = SPEK_AUDIO_CANNOT_OPEN_FILE;
- return cx;
+ AVFormatContext *format_context = nullptr;
+ if (avformat_open_input(&format_context, file_name.c_str(), nullptr, nullptr) != 0) {
+ error = AudioError::CANNOT_OPEN_FILE;
}
- if (avformat_find_stream_info(cx->format_context, NULL) < 0) {
+
+ if (!error && avformat_find_stream_info(format_context, nullptr) < 0) {
// 24-bit APE returns an error but parses the stream info just fine.
- if (cx->format_context->nb_streams <= 0) {
- cx->properties.error = SPEK_AUDIO_NO_STREAMS;
- return cx;
+ // TODO: old comment, verify
+ if (format_context->nb_streams <= 0) {
+ error = AudioError::NO_STREAMS;
}
}
- cx->audio_stream = -1;
- for (int i = 0; i < cx->format_context->nb_streams; i++) {
- if (cx->format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
- cx->audio_stream = i;
- break;
+
+ int audio_stream = -1;
+ if (!error) {
+ for (unsigned int i = 0; i < format_context->nb_streams; i++) {
+ if (format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+ audio_stream = i;
+ break;
+ }
}
}
- if (cx->audio_stream == -1) {
- cx->properties.error = SPEK_AUDIO_NO_AUDIO;
- return cx;
+ if (audio_stream == -1) {
+ error = AudioError::NO_AUDIO;
+ }
+
+ AVStream *stream = nullptr;
+ AVCodecContext *codec_context = nullptr;
+ AVCodec *codec = nullptr;
+ if (!error) {
+ stream = format_context->streams[audio_stream];
+ codec_context = stream->codec;
+ codec = avcodec_find_decoder(codec_context->codec_id);
+ if (!codec) {
+ error = AudioError::NO_DECODER;
+ }
+ }
+
+ std::string codec_name;
+ int bit_rate = 0;
+ int sample_rate = 0;
+ int bits_per_sample = 0;
+ int channels = 0;
+ double duration = 0;
+ if (!error) {
+ // We can already fill in the stream info even if the codec won't be able to open it.
+ codec_name = codec->long_name;
+ bit_rate = codec_context->bit_rate;
+ sample_rate = codec_context->sample_rate;
+ bits_per_sample = codec_context->bits_per_raw_sample;
+ if (!bits_per_sample) {
+ // APE uses bpcs, FLAC uses bprs.
+ // TODO: old comment, verify
+ bits_per_sample = codec_context->bits_per_coded_sample;
+ }
+ channels = codec_context->channels;
+
+ if (stream->duration != AV_NOPTS_VALUE) {
+ duration = stream->duration * av_q2d(stream->time_base);
+ } else if (format_context->duration != AV_NOPTS_VALUE) {
+ duration = format_context->duration / (double) AV_TIME_BASE;
+ } else {
+ error = AudioError::NO_DURATION;
+ }
+
+ if (!error && channels <= 0) {
+ error = AudioError::NO_CHANNELS;
+ }
}
- cx->stream = cx->format_context->streams[cx->audio_stream];
- cx->codec_context = cx->stream->codec;
- cx->codec = avcodec_find_decoder(cx->codec_context->codec_id);
- if (cx->codec == NULL) {
- cx->properties.error = SPEK_AUDIO_NO_DECODER;
- return cx;
+
+ if (!error && avcodec_open2(codec_context, codec, nullptr) < 0) {
+ error = AudioError::CANNOT_OPEN_DECODER;
}
- // We can already fill in the stream info even if the codec won't be able to open it.
- cx->properties.codec_name = strdup(cx->codec->long_name);
- cx->properties.bit_rate = cx->codec_context->bit_rate;
- cx->properties.sample_rate = cx->codec_context->sample_rate;
- cx->properties.bits_per_sample = cx->codec_context->bits_per_raw_sample;
- if (!cx->properties.bits_per_sample) {
- // APE uses bpcs, FLAC uses bprs.
- cx->properties.bits_per_sample = cx->codec_context->bits_per_coded_sample;
+
+ bool is_planar = false;
+ int width = 0;
+ bool fp = false;
+ if (!error) {
+ is_planar = av_sample_fmt_is_planar(codec_context->sample_fmt);
+ width = av_get_bytes_per_sample(codec_context->sample_fmt);
+ AVSampleFormat fmt = codec_context->sample_fmt;
+ if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P ||
+ fmt == AV_SAMPLE_FMT_S32 || fmt == AV_SAMPLE_FMT_S32P) {
+ fp = false;
+ } else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP ||
+ fmt == AV_SAMPLE_FMT_DBL || fmt == AV_SAMPLE_FMT_DBLP ) {
+ fp = true;
+ } else {
+ error = AudioError::BAD_SAMPLE_FORMAT;
+ }
}
- cx->properties.channels = cx->codec_context->channels;
- if (cx->stream->duration != AV_NOPTS_VALUE) {
- cx->properties.duration = cx->stream->duration * av_q2d(cx->stream->time_base);
- } else if (cx->format_context->duration != AV_NOPTS_VALUE) {
- cx->properties.duration = cx->format_context->duration / (double) AV_TIME_BASE;
- } else {
- cx->properties.error = SPEK_AUDIO_NO_DURATION;
- return cx;
+
+ return std::unique_ptr<AudioFile>(new AudioFileImpl(
+ error, format_context, audio_stream,
+ codec_name, bit_rate, sample_rate, bits_per_sample,
+ channels, duration, is_planar, width, fp
+ ));
+}
+
+AudioFileImpl::AudioFileImpl(
+ AudioError error, AVFormatContext *format_context, int audio_stream,
+ const std::string& codec_name, int bit_rate, int sample_rate, int bits_per_sample,
+ int channels, double duration, bool is_planar, int width, bool fp
+) :
+ error(error), format_context(format_context), audio_stream(audio_stream),
+ codec_name(codec_name), bit_rate(bit_rate),
+ sample_rate(sample_rate), bits_per_sample(bits_per_sample),
+ channels(channels), duration(duration), is_planar(is_planar), width(width), fp(fp)
+{
+ av_init_packet(&this->packet);
+ this->packet.data = nullptr;
+ this->packet.size = 0;
+ this->offset = 0;
+ this->frame = avcodec_alloc_frame();
+ this->buffer_size = 0;
+ this->buffer = nullptr;
+ this->frames_per_interval = 0;
+ this->error_per_interval = 0;
+ this->error_base = 0;
+}
+
+AudioFileImpl::~AudioFileImpl()
+{
+ if (this->buffer) {
+ av_freep(&this->buffer);
}
- if (cx->properties.channels <= 0) {
- cx->properties.error = SPEK_AUDIO_NO_CHANNELS;
- return cx;
+ if (this->frame) {
+ avcodec_free_frame(&this->frame);
}
- if (avcodec_open2(cx->codec_context, cx->codec, NULL) < 0) {
- cx->properties.error = SPEK_AUDIO_CANNOT_OPEN_DECODER;
- return cx;
+ if (this->packet.data) {
+ this->packet.data -= this->offset;
+ this->packet.size += this->offset;
+ this->offset = 0;
+ av_free_packet(&this->packet);
}
- cx->is_planar = av_sample_fmt_is_planar(cx->codec_context->sample_fmt);
- cx->properties.width = av_get_bytes_per_sample(cx->codec_context->sample_fmt);
- switch (cx->codec_context->sample_fmt) {
- case AV_SAMPLE_FMT_S16:
- case AV_SAMPLE_FMT_S16P:
- case AV_SAMPLE_FMT_S32:
- case AV_SAMPLE_FMT_S32P:
- cx->properties.fp = false;
- break;
- case AV_SAMPLE_FMT_FLT:
- case AV_SAMPLE_FMT_FLTP:
- case AV_SAMPLE_FMT_DBL:
- case AV_SAMPLE_FMT_DBLP:
- cx->properties.fp = true;
- break;
- default:
- cx->properties.error = SPEK_AUDIO_BAD_SAMPLE_FORMAT;
- return cx;
+ if (this->format_context) {
+ avformat_close_input(&this->format_context);
}
- cx->buffer_size = 0;
- cx->properties.buffer = NULL;
- av_init_packet(&cx->packet);
- cx->frame = avcodec_alloc_frame();
- cx->offset = 0;
- return cx;
}
-void spek_audio_start(struct spek_audio_context *cx, int samples)
+void AudioFileImpl::start(int samples)
{
- int64_t rate = cx->properties.sample_rate * (int64_t) cx->stream->time_base.num;
- int64_t duration = (int64_t)
- (cx->properties.duration * cx->stream->time_base.den / cx->stream->time_base.num);
- cx->properties.error_base = samples * (int64_t)cx->stream->time_base.den;
- cx->properties.frames_per_interval = av_rescale_rnd(
- duration, rate, cx->properties.error_base, AV_ROUND_DOWN);
- cx->properties.error_per_interval = (duration * rate) % cx->properties.error_base;
+ AVStream *stream = this->format_context->streams[this->audio_stream];
+ int64_t rate = this->sample_rate * (int64_t)stream->time_base.num;
+ int64_t duration = (int64_t)(this->duration * stream->time_base.den / stream->time_base.num);
+ this->error_base = samples * (int64_t)stream->time_base.den;
+ this->frames_per_interval = av_rescale_rnd(duration, rate, this->error_base, AV_ROUND_DOWN);
+ this->error_per_interval = (duration * rate) % this->error_base;
}
-int spek_audio_read(struct spek_audio_context *cx) {
- if (cx->properties.error) {
+int AudioFileImpl::read()
+{
+ if (!!this->error) {
return -1;
}
for (;;) {
- while (cx->packet.size > 0) {
- avcodec_get_frame_defaults(cx->frame);
+ while (this->packet.size > 0) {
+ avcodec_get_frame_defaults(this->frame);
+ auto codec_context = this->format_context->streams[this->audio_stream]->codec;
int got_frame = 0;
- int len = avcodec_decode_audio4(cx->codec_context, cx->frame, &got_frame, &cx->packet);
+ int len = avcodec_decode_audio4(codec_context, this->frame, &got_frame, &this->packet);
if (len < 0) {
// Error, skip the frame.
break;
}
- cx->packet.data += len;
- cx->packet.size -= len;
- cx->offset += len;
+ this->packet.data += len;
+ this->packet.size -= len;
+ this->offset += len;
if (!got_frame) {
// No data yet, get more frames.
continue;
}
// We have data, return it and come back for more later.
- int samples = cx->frame->nb_samples;
- int channels = cx->properties.channels;
- int width = cx->properties.width;
+ int samples = this->frame->nb_samples;
+ int channels = this->channels;
+ int width = this->width;
int buffer_size = samples * channels * width;
- if (buffer_size > cx->buffer_size) {
- cx->properties.buffer = (uint8_t*)av_realloc(cx->properties.buffer, buffer_size);
- cx->buffer_size = buffer_size;
+ if (buffer_size > this->buffer_size) {
+ this->buffer = (uint8_t*)av_realloc(this->buffer, buffer_size);
+ this->buffer_size = buffer_size;
}
- if (cx->is_planar) {
+ if (this->is_planar) {
for (int channel = 0; channel < channels; ++channel) {
- uint8_t *buffer = cx->properties.buffer + channel * width;
- uint8_t *data = cx->frame->data[channel];
+ uint8_t *buffer = this->buffer + channel * width;
+ uint8_t *data = this->frame->data[channel];
for (int sample = 0; sample < samples; ++sample) {
for (int i = 0; i < width; ++i) {
*buffer++ = *data++;
@@ -195,23 +280,23 @@ int spek_audio_read(struct spek_audio_context *cx) {
}
}
} else {
- memcpy(cx->properties.buffer, cx->frame->data[0], buffer_size);
+ memcpy(this->buffer, this->frame->data[0], buffer_size);
}
return buffer_size;
}
- if (cx->packet.data) {
- cx->packet.data -= cx->offset;
- cx->packet.size += cx->offset;
- cx->offset = 0;
- av_free_packet (&cx->packet);
+ if (this->packet.data) {
+ this->packet.data -= this->offset;
+ this->packet.size += this->offset;
+ this->offset = 0;
+ av_free_packet(&this->packet);
}
int res = 0;
- while ((res = av_read_frame(cx->format_context, &cx->packet)) >= 0) {
- if (cx->packet.stream_index == cx->audio_stream) {
+ while ((res = av_read_frame(this->format_context, &this->packet)) >= 0) {
+ if (this->packet.stream_index == this->audio_stream) {
break;
}
- av_free_packet(&cx->packet);
+ av_free_packet(&this->packet);
}
if (res < 0) {
// End of file or error.
@@ -219,29 +304,3 @@ int spek_audio_read(struct spek_audio_context *cx) {
}
}
}
-
-void spek_audio_close(struct spek_audio_context *cx)
-{
- if (cx->properties.codec_name != NULL) {
- free(cx->properties.codec_name);
- }
- if (cx->frame != NULL) {
- avcodec_free_frame(&cx->frame);
- }
- if (cx->packet.data) {
- cx->packet.data -= cx->offset;
- cx->packet.size += cx->offset;
- cx->offset = 0;
- av_free_packet(&cx->packet);
- }
- if (cx->properties.buffer) {
- av_freep(&cx->properties.buffer);
- }
- if (cx->codec_context != NULL) {
- avcodec_close(cx->codec_context);
- }
- if (cx->format_context != NULL) {
- avformat_close_input(&cx->format_context);
- }
- free(cx);
-}
diff --git a/src/spek-audio.h b/src/spek-audio.h
@@ -19,61 +19,58 @@
#ifndef SPEK_AUDIO_H_
#define SPEK_AUDIO_H_
-#include <tr1/cstdint>
+#include <memory>
#include <string>
-struct spek_audio_context;
+class AudioFile;
+enum class AudioError;
-enum spek_audio_error
+class Audio
{
- SPEK_AUDIO_OK = 0,
- SPEK_AUDIO_CANNOT_OPEN_FILE,
- SPEK_AUDIO_NO_STREAMS,
- SPEK_AUDIO_NO_AUDIO,
- SPEK_AUDIO_NO_DECODER,
- SPEK_AUDIO_NO_DURATION,
- SPEK_AUDIO_NO_CHANNELS,
- SPEK_AUDIO_CANNOT_OPEN_DECODER,
- SPEK_AUDIO_BAD_SAMPLE_FORMAT,
-};
+public:
+ Audio();
-struct spek_audio_properties
-{
- char *codec_name;
- enum spek_audio_error error;
- int bit_rate;
- int sample_rate;
- int bits_per_sample;
- int width; // number of bits used to store a sample
- bool fp; // floating-point sample representation
- int channels;
- double duration;
- // TODO: these four guys don't belong here, move them somewhere else when revamping the pipeline
- uint8_t *buffer;
- int64_t frames_per_interval;
- int64_t error_per_interval;
- int64_t error_base;
+ std::unique_ptr<AudioFile> open(const std::string& file_name);
};
-// Initialise FFmpeg, should be called once on start up.
-void spek_audio_init();
-
-// Open the file, check if it has an audio stream which can be decoded.
-// On error, initialises the `error` field in the returned context.
-struct spek_audio_context * spek_audio_open(const char *path);
+class AudioFile
+{
+public:
+ virtual ~AudioFile() {}
-const struct spek_audio_properties * spek_audio_get_properties(struct spek_audio_context *cs);
+ virtual void start(int samples) = 0;
+ virtual int read() = 0;
-// Prepare the context for reading audio samples.
-void spek_audio_start(struct spek_audio_context *cx, int samples);
+ virtual AudioError get_error() const = 0;
+ virtual std::string get_codec_name() const = 0;
+ virtual int get_bit_rate() const = 0;
+ virtual int get_sample_rate() const = 0;
+ virtual int get_bits_per_sample() const = 0;
+ virtual int get_channels() const = 0;
+ virtual double get_duration() const = 0;
+ virtual int get_width() const = 0;
+ virtual bool get_fp() const = 0;
+ virtual const uint8_t *get_buffer() const = 0;
+ virtual int64_t get_frames_per_interval() const = 0;
+ virtual int64_t get_error_per_interval() const = 0;
+ virtual int64_t get_error_base() const = 0;
+};
-// Read and decode the opened audio stream.
-// Returns -1 on error, 0 if there's nothing left to read
-// or the number of bytes decoded into the buffer.
-int spek_audio_read(struct spek_audio_context *cx);
+enum class AudioError
+{
+ OK,
+ CANNOT_OPEN_FILE,
+ NO_STREAMS,
+ NO_AUDIO,
+ NO_DECODER,
+ NO_DURATION,
+ NO_CHANNELS,
+ CANNOT_OPEN_DECODER,
+ BAD_SAMPLE_FORMAT,
+};
-// Closes the file opened with spek_audio_open,
-// frees all allocated buffers and the context
-void spek_audio_close(struct spek_audio_context *cx);
+inline bool operator!(AudioError error) {
+ return error == AudioError::OK;
+}
#endif
diff --git a/src/spek-events.cc b/src/spek-events.cc
@@ -1,6 +1,6 @@
/* spek-events.cc
*
- * Copyright (C) 2012 Alexander Kojevnikov <alexander@kojevnikov.com>
+ * Copyright (C) 2012-2013 Alexander Kojevnikov <alexander@kojevnikov.com>
*
* Spek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,7 +27,7 @@ SpekHaveSampleEvent::SpekHaveSampleEvent(int bands, int sample, float *values, b
SetEventType(SPEK_HAVE_SAMPLE);
}
-SpekHaveSampleEvent::SpekHaveSampleEvent(const SpekHaveSampleEvent& other)
+SpekHaveSampleEvent::SpekHaveSampleEvent(const SpekHaveSampleEvent& other) : wxEvent(other)
{
SetEventType(SPEK_HAVE_SAMPLE);
this->bands = other.bands;
diff --git a/src/spek-pipeline.cc b/src/spek-pipeline.cc
@@ -1,6 +1,6 @@
/* spek-pipeline.cc
*
- * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com>
+ * Copyright (C) 2010-2013 Alexander Kojevnikov <alexander@kojevnikov.com>
*
* Spek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,11 +32,17 @@
#include <stdlib.h>
#include <string.h>
+#include <vector>
+
+#include <wx/intl.h>
+
#include "spek-audio.h"
#include "spek-fft.h"
#include "spek-pipeline.h"
+#define ngettext wxPLURAL
+
enum
{
NFFT = 64 // Number of FFTs to pre-fetch.
@@ -44,8 +50,7 @@ enum
struct spek_pipeline
{
- struct spek_audio_context *cx;
- const struct spek_audio_properties *properties;
+ std::unique_ptr<AudioFile> file;
int bands;
int samples;
spek_pipeline_cb cb;
@@ -79,14 +84,13 @@ struct spek_pipeline
static void * reader_func(void *);
static void * worker_func(void *);
static void reader_sync(struct spek_pipeline *p, int pos);
-static float average_input(const struct spek_pipeline *p, void *buffer);
+static float average_input(const struct spek_pipeline *p, const void *buffer);
struct spek_pipeline * spek_pipeline_open(
- const char *path, int bands, int samples, spek_pipeline_cb cb, void *cb_data)
+ std::unique_ptr<AudioFile> file, int bands, int samples, spek_pipeline_cb cb, void *cb_data)
{
- struct spek_pipeline *p = (spek_pipeline*)malloc(sizeof(struct spek_pipeline));
- p->cx = spek_audio_open(path);
- p->properties = spek_audio_get_properties(p->cx);
+ spek_pipeline *p = new spek_pipeline();
+ p->file = std::move(file);
p->bands = bands;
p->samples = samples;
p->cb = cb;
@@ -103,7 +107,7 @@ struct spek_pipeline * spek_pipeline_open(
p->has_worker_mutex = false;
p->has_worker_cond = false;
- if (!p->properties->error) {
+ if (!p->file->get_error()) {
p->nfft = 2 * bands - 2;
p->coss = (float*)malloc(p->nfft * sizeof(float));
float cf = 2.0f * (float)M_PI / p->nfft;
@@ -114,20 +118,17 @@ struct spek_pipeline * spek_pipeline_open(
p->input_size = p->nfft * (NFFT * 2 + 1);
p->input = (float*)malloc(p->input_size * sizeof(float));
p->output = (float*)malloc(bands * sizeof(float));
- spek_audio_start(p->cx, samples);
+ p->file->start(samples);
}
return p;
}
-const struct spek_audio_properties * spek_pipeline_properties(struct spek_pipeline *pipeline)
-{
- return pipeline->properties;
-}
-
void spek_pipeline_start(struct spek_pipeline *p)
{
- if (p->properties->error) return;
+ if (!!p->file->get_error()) {
+ return;
+ }
p->input_pos = 0;
p->worker_done = false;
@@ -183,11 +184,106 @@ void spek_pipeline_close(struct spek_pipeline *p)
free(p->coss);
p->coss = NULL;
}
- if (p->cx) {
- spek_audio_close(p->cx);
- p->cx = NULL;
+
+ p->file.reset();
+
+ delete p;
+}
+
+std::string spek_pipeline_desc(const struct spek_pipeline *pipeline)
+{
+ std::vector<std::string> items;
+
+ if (!pipeline->file->get_codec_name().empty()) {
+ items.push_back(pipeline->file->get_codec_name());
+ }
+ if (pipeline->file->get_bit_rate()) {
+ items.push_back(std::string(
+ wxString::Format(_("%d kbps"), (pipeline->file->get_bit_rate() + 500) / 1000).utf8_str()
+ ));
+ }
+ if (pipeline->file->get_sample_rate()) {
+ items.push_back(std::string(
+ wxString::Format(_("%d Hz"), pipeline->file->get_sample_rate()).utf8_str()
+ ));
+ }
+ // Include bits per sample only if there is no bitrate.
+ if (pipeline->file->get_bits_per_sample() && !pipeline->file->get_bit_rate()) {
+ items.push_back(std::string(
+ wxString::Format(
+ ngettext("%d bit", "%d bits", pipeline->file->get_bits_per_sample()),
+ pipeline->file->get_bits_per_sample()
+ ).utf8_str()
+ ));
}
- free(p);
+ if (pipeline->file->get_channels()) {
+ items.push_back(std::string(
+ wxString::Format(
+ ngettext("%d channel", "%d channels", pipeline->file->get_channels()),
+ pipeline->file->get_channels()
+ ).utf8_str()
+ ));
+ }
+
+ std::string desc;
+ for (const auto& item : items) {
+ if (!desc.empty()) {
+ desc.append(", ");
+ }
+ desc.append(item);
+ }
+
+ wxString error;
+ switch (pipeline->file->get_error()) {
+ case AudioError::CANNOT_OPEN_FILE:
+ error = _("Cannot open input file");
+ break;
+ case AudioError::NO_STREAMS:
+ error = _("Cannot find stream info");
+ break;
+ case AudioError::NO_AUDIO:
+ error = _("The file contains no audio streams");
+ break;
+ case AudioError::NO_DECODER:
+ error = _("Cannot find decoder");
+ break;
+ case AudioError::NO_DURATION:
+ error = _("Unknown duration");
+ break;
+ case AudioError::NO_CHANNELS:
+ error = _("No audio channels");
+ break;
+ case AudioError::CANNOT_OPEN_DECODER:
+ error = _("Cannot open decoder");
+ break;
+ case AudioError::BAD_SAMPLE_FORMAT:
+ error = _("Unsupported sample format");
+ break;
+ case AudioError::OK:
+ break;
+ }
+
+ auto error_string = std::string(error.utf8_str());
+ if (desc.empty()) {
+ desc = error_string;
+ } else if (!error_string.empty()) {
+ // TRANSLATORS: first %s is the error message, second %s is stream description.
+ desc = std::string(
+ wxString::Format(_("%s: %s"), error_string.c_str(), desc.c_str()).utf8_str()
+ );
+ }
+
+ return desc;
+}
+
+double spek_pipeline_duration(const struct spek_pipeline *pipeline)
+{
+ return pipeline->file->get_duration();
+}
+
+int spek_pipeline_sample_rate(const struct spek_pipeline *pipeline)
+{
+ return pipeline->file->get_sample_rate();
}
static void * reader_func(void *pp)
@@ -200,12 +296,12 @@ static void * reader_func(void *pp)
}
int pos = 0, prev_pos = 0;
- int block_size = p->properties->width * p->properties->channels;
+ int block_size = p->file->get_width() * p->file->get_channels();
int size;
- while ((size = spek_audio_read(p->cx)) > 0) {
+ while ((size = p->file->read()) > 0) {
if (p->quit) break;
- uint8_t *buffer = p->properties->buffer;
+ const uint8_t *buffer = p->file->get_buffer();
while (size >= block_size) {
p->input[pos] = average_input(p, buffer);
buffer += block_size;
@@ -290,11 +386,11 @@ static void * worker_func(void *pp)
// If we have enough frames for an FFT or we have
// all frames required for the interval run and FFT.
bool int_full =
- acc_error < p->properties->error_base &&
- frames == p->properties->frames_per_interval;
+ acc_error < p->file->get_error_base() &&
+ frames == p->file->get_frames_per_interval();
bool int_over =
- acc_error >= p->properties->error_base &&
- frames == 1 + p->properties->frames_per_interval;
+ acc_error >= p->file->get_error_base() &&
+ frames == 1 + p->file->get_frames_per_interval();
if (frames % p->nfft == 0 || ((int_full || int_over) && num_fft == 0)) {
prev_head = head;
@@ -317,9 +413,9 @@ static void * worker_func(void *pp)
// Do we have the FFTs for one interval?
if (int_full || int_over) {
if (int_over) {
- acc_error -= p->properties->error_base;
+ acc_error -= p->file->get_error_base();
} else {
- acc_error += p->properties->error_per_interval;
+ acc_error += p->file->get_error_per_interval();
}
for (int i = 0; i < p->bands; i++) {
@@ -337,31 +433,31 @@ static void * worker_func(void *pp)
}
}
-static float average_input(const struct spek_pipeline *p, void *buffer)
+static float average_input(const struct spek_pipeline *p, const void *buffer)
{
- int channels = p->properties->channels;
+ int channels = p->file->get_channels();
float res = 0.0f;
- if (p->properties->fp) {
- if (p->properties->width == 4) {
+ if (p->file->get_fp()) {
+ if (p->file->get_width() == 4) {
float *b = (float*)buffer;
for (int i = 0; i < channels; i++) {
res += b[i];
}
} else {
- assert(p->properties->width == 8);
+ assert(p->file->get_width() == 8);
double *b = (double*)buffer;
for (int i = 0; i < channels; i++) {
res += (float) b[i];
}
}
} else {
- if (p->properties->width == 2) {
+ if (p->file->get_width() == 2) {
int16_t *b = (int16_t*)buffer;
for (int i = 0; i < channels; i++) {
res += b[i] / (float) INT16_MAX;
}
} else {
- assert (p->properties->width == 4);
+ assert (p->file->get_width() == 4);
int32_t *b = (int32_t*)buffer;
for (int i = 0; i < channels; i++) {
res += b[i] / (float) INT32_MAX;
diff --git a/src/spek-pipeline.h b/src/spek-pipeline.h
@@ -1,6 +1,6 @@
/* spek-pipeline.h
*
- * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com>
+ * Copyright (C) 2010-2013 Alexander Kojevnikov <alexander@kojevnikov.com>
*
* Spek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,18 +19,23 @@
#ifndef SPEK_PIPELINE_H_
#define SPEK_PIPELINE_H_
+#include <memory>
+#include <string>
+
+class Audio;
struct spek_pipeline;
-struct spek_audio_properties;
typedef void (*spek_pipeline_cb)(int sample, float *values, void *cb_data);
struct spek_pipeline * spek_pipeline_open(
- const char *path, int bands, int samples, spek_pipeline_cb cb, void *cb_data);
-
-const struct spek_audio_properties * spek_pipeline_properties(struct spek_pipeline *pipeline);
+ std::unique_ptr<AudioFile> file, int bands, int samples, spek_pipeline_cb cb, void *cb_data
+);
void spek_pipeline_start(struct spek_pipeline *pipeline);
-
void spek_pipeline_close(struct spek_pipeline *pipeline);
+std::string spek_pipeline_desc(const struct spek_pipeline *pipeline);
+double spek_pipeline_duration(const struct spek_pipeline *pipeline);
+int spek_pipeline_sample_rate(const struct spek_pipeline *pipeline);
+
#endif
diff --git a/src/spek-preferences-dialog.cc b/src/spek-preferences-dialog.cc
@@ -86,7 +86,7 @@ SpekPreferencesDialog::SpekPreferencesDialog(wxWindow *parent) :
language_sizer->Add(language_choice, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 12);
int active_index = 0;
wxString active_language = SpekPreferences::get().get_language();
- for (int i = 0; i < this->languages.GetCount(); i += 2) {
+ for (unsigned int i = 0; i < this->languages.GetCount(); i += 2) {
language_choice->Append(this->languages[i + 1]);
if (this->languages[i] == active_language) {
active_index = i / 2;
diff --git a/src/spek-spectrogram.cc b/src/spek-spectrogram.cc
@@ -1,6 +1,6 @@
/* spek-spectrogram.cc
*
- * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com>
+ * Copyright (C) 2010-2013 Alexander Kojevnikov <alexander@kojevnikov.com>
*
* Spek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,15 +20,13 @@
#include <wx/dcbuffer.h>
-#include <spek-audio.h>
-#include <spek-palette.h>
-#include <spek-pipeline.h>
-#include <spek-utils.h>
-
-#include "spek-audio-desc.h"
+#include "spek-audio.h"
#include "spek-events.h"
+#include "spek-palette.h"
+#include "spek-pipeline.h"
#include "spek-platform.h"
#include "spek-ruler.h"
+#include "spek-utils.h"
#include "spek-spectrogram.h"
@@ -63,6 +61,7 @@ SpekSpectrogram::SpekSpectrogram(wxFrame *parent) :
parent, -1, wxDefaultPosition, wxDefaultSize,
wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS
),
+ audio(new Audio()), // TODO: refactor
pipeline(NULL),
duration(0.0),
sample_rate(0),
@@ -111,7 +110,6 @@ void SpekSpectrogram::save(const wxString& path)
void SpekSpectrogram::on_char(wxKeyEvent& evt)
{
bool C = evt.GetModifiers() == wxMOD_CONTROL;
- bool S = evt.GetModifiers() == wxMOD_SHIFT;
bool CS = evt.GetModifiers() == (wxMOD_CONTROL | wxMOD_SHIFT);
bool dn = evt.GetKeyCode() == WXK_DOWN;
bool up = evt.GetKeyCode() == WXK_UP;
@@ -133,13 +131,13 @@ void SpekSpectrogram::on_char(wxKeyEvent& evt)
Refresh();
}
-void SpekSpectrogram::on_paint(wxPaintEvent& evt)
+void SpekSpectrogram::on_paint(wxPaintEvent&)
{
wxAutoBufferedPaintDC dc(this);
render(dc);
}
-void SpekSpectrogram::on_size(wxSizeEvent& evt)
+void SpekSpectrogram::on_size(wxSizeEvent&)
{
wxSize size = GetClientSize();
bool width_changed = this->prev_width != size.GetWidth();
@@ -366,17 +364,17 @@ void SpekSpectrogram::start()
if (samples > 0) {
this->image.Create(samples, BANDS);
this->pipeline = spek_pipeline_open(
- this->path.utf8_str(),
+ this->audio->open(std::string(this->path.utf8_str())),
BANDS,
samples,
pipeline_cb,
this
);
spek_pipeline_start(this->pipeline);
- const spek_audio_properties *properties = spek_pipeline_properties(this->pipeline);
- this->desc = spek_audio_desc(properties);
- this->duration = properties->duration;
- this->sample_rate = properties->sample_rate;
+ // TODO: extract conversion into a utility function.
+ this->desc = wxString::FromUTF8(spek_pipeline_desc(this->pipeline).c_str());
+ this->duration = spek_pipeline_duration(this->pipeline);
+ this->sample_rate = spek_pipeline_sample_rate(this->pipeline);
} else {
this->image.Create(1, 1);
}
diff --git a/src/spek-spectrogram.h b/src/spek-spectrogram.h
@@ -1,6 +1,6 @@
/* spek-spectrogram.h
*
- * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com>
+ * Copyright (C) 2010-2013 Alexander Kojevnikov <alexander@kojevnikov.com>
*
* Spek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,10 +19,12 @@
#ifndef SPEK_SPECTROGRAM_H_
#define SPEK_SPECTROGRAM_H_
+#include <memory>
+
#include <wx/wx.h>
+class Audio;
class SpekHaveSampleEvent;
-struct spek_audio_properties;
struct spek_pipeline;
class SpekSpectrogram : public wxWindow
@@ -43,6 +45,7 @@ private:
void start();
void stop();
+ std::unique_ptr<Audio> audio;
spek_pipeline *pipeline;
wxString path;
wxString desc;
diff --git a/src/spek-window.cc b/src/spek-window.cc
@@ -57,7 +57,7 @@ public:
SpekDropTarget(SpekWindow *window) : wxFileDropTarget(), window(window) {}
protected:
- virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames){
+ virtual bool OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames){
if (filenames.GetCount() == 1) {
window->open(filenames[0]);
return true;
@@ -70,7 +70,7 @@ private:
};
SpekWindow::SpekWindow(const wxString& path) :
- path(path), wxFrame(NULL, -1, wxEmptyString, wxDefaultPosition, wxSize(640, 480))
+ wxFrame(NULL, -1, wxEmptyString, wxDefaultPosition, wxSize(640, 480)), path(path)
{
this->description = _("Spek - Acoustic Spectrum Analyser");
SetTitle(this->description);
@@ -221,7 +221,7 @@ static const char *audio_extensions[] = {
NULL
};
-void SpekWindow::on_open(wxCommandEvent& event)
+void SpekWindow::on_open(wxCommandEvent&)
{
static wxString filters = wxEmptyString;
static int filter_index = 1;
@@ -261,7 +261,7 @@ void SpekWindow::on_open(wxCommandEvent& event)
dlg->Destroy();
}
-void SpekWindow::on_save(wxCommandEvent& event)
+void SpekWindow::on_save(wxCommandEvent&)
{
static wxString filters = wxEmptyString;
@@ -296,25 +296,25 @@ void SpekWindow::on_save(wxCommandEvent& event)
dlg->Destroy();
}
-void SpekWindow::on_exit(wxCommandEvent& event)
+void SpekWindow::on_exit(wxCommandEvent&)
{
Close(true);
}
-void SpekWindow::on_preferences(wxCommandEvent& event)
+void SpekWindow::on_preferences(wxCommandEvent&)
{
SpekPreferencesDialog dlg(this);
dlg.ShowModal();
}
-void SpekWindow::on_help(wxCommandEvent& event)
+void SpekWindow::on_help(wxCommandEvent&)
{
wxLaunchDefaultBrowser(
wxString::Format(wxT("http://spek-project.org/man-%s.html"), wxT(PACKAGE_VERSION))
);
}
-void SpekWindow::on_about(wxCommandEvent& event)
+void SpekWindow::on_about(wxCommandEvent&)
{
wxAboutDialogInfo info;
info.AddDeveloper(wxT("Alexander Kojevnikov"));
@@ -341,13 +341,13 @@ void SpekWindow::on_about(wxCommandEvent& event)
wxAboutBox(info);
}
-void SpekWindow::on_notify(wxCommandEvent& event)
+void SpekWindow::on_notify(wxCommandEvent&)
{
this->GetSizer()->Show((size_t)0);
this->Layout();
}
-void SpekWindow::on_visit(wxCommandEvent& event)
+void SpekWindow::on_visit(wxCommandEvent&)
{
wxLaunchDefaultBrowser(wxT("http://spek-project.org"));
}
diff --git a/src/spek.cc b/src/spek.cc
@@ -20,8 +20,6 @@
#include <wx/log.h>
#include <wx/socket.h>
-#include <spek-audio.h>
-
#include "spek-artwork.h"
#include "spek-platform.h"
#include "spek-preferences.h"
@@ -53,8 +51,6 @@ bool Spek::OnInit()
wxInitAllImageHandlers();
wxSocketBase::Initialize();
- spek_audio_init();
-
spek_artwork_init();
spek_platform_init();
SpekPreferences::get().init();
@@ -70,7 +66,9 @@ bool Spek::OnInit()
wxCMD_LINE_SWITCH,
wxT_2("V"),
wxT_2("version"),
- wxT_2("Display the version and exit")
+ wxT_2("Display the version and exit"),
+ wxCMD_LINE_VAL_NONE,
+ wxCMD_LINE_PARAM_OPTIONAL
}, {
wxCMD_LINE_PARAM,
NULL,