spek

Acoustic spectrum analyser
git clone http://git.hanabi.in/repos/spek.git
Log | Files | Refs | README

commit da184788d11e6de9d1c316027c591d37ff2ce97e
parent a75d6408e8687bccdbc52d5d823b414d7b83f097
Author: Alexander Kojevnikov <alexander@kojevnikov.com>
Date:   Tue, 19 Feb 2013 10:10:24 -0800

C++ify spek_audio

Diffstat:
Mconfigure.ac | 4++--
Msrc/Makefile.am | 8++++----
Dsrc/spek-audio-desc.cc | 103-------------------------------------------------------------------------------
Dsrc/spek-audio-desc.h | 28----------------------------
Msrc/spek-audio.cc | 355++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/spek-audio.h | 87++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/spek-events.cc | 4++--
Msrc/spek-pipeline.cc | 168++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/spek-pipeline.h | 17+++++++++++------
Msrc/spek-preferences-dialog.cc | 2+-
Msrc/spek-spectrogram.cc | 28+++++++++++++---------------
Msrc/spek-spectrogram.h | 7+++++--
Msrc/spek-window.cc | 20++++++++++----------
Msrc/spek.cc | 8+++-----
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,