C++ify spek_audio

This commit is contained in:
Alexander Kojevnikov 2013-02-19 10:10:24 -08:00
parent a75d6408e8
commit da184788d1
14 changed files with 435 additions and 410 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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;
}

View File

@ -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

View File

@ -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)
std::unique_ptr<AudioFile> Audio::open(const std::string& file_name)
{
return &cx->properties;
}
AudioError error = AudioError::OK;
struct spek_audio_context * spek_audio_open(const char *path)
{
// TODO: malloc and initialise explicitely
struct spek_audio_context *cx =
(spek_audio_context *)calloc(1, sizeof(struct spek_audio_context));
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;
}
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;
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;
}
}
// 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;
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->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;
if (!error && avcodec_open2(codec_context, codec, nullptr) < 0) {
error = AudioError::CANNOT_OPEN_DECODER;
}
if (cx->properties.channels <= 0) {
cx->properties.error = SPEK_AUDIO_NO_CHANNELS;
return cx;
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;
}
}
if (avcodec_open2(cx->codec_context, cx->codec, NULL) < 0) {
cx->properties.error = SPEK_AUDIO_CANNOT_OPEN_DECODER;
return cx;
}
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;
}
cx->buffer_size = 0;
cx->properties.buffer = NULL;
av_init_packet(&cx->packet);
cx->frame = avcodec_alloc_frame();
cx->offset = 0;
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
));
}
void spek_audio_start(struct spek_audio_context *cx, int samples)
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)
{
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;
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;
}
int spek_audio_read(struct spek_audio_context *cx) {
if (cx->properties.error) {
AudioFileImpl::~AudioFileImpl()
{
if (this->buffer) {
av_freep(&this->buffer);
}
if (this->frame) {
avcodec_free_frame(&this->frame);
}
if (this->packet.data) {
this->packet.data -= this->offset;
this->packet.size += this->offset;
this->offset = 0;
av_free_packet(&this->packet);
}
if (this->format_context) {
avformat_close_input(&this->format_context);
}
}
void AudioFileImpl::start(int samples)
{
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 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);
}

View File

@ -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();
std::unique_ptr<AudioFile> open(const std::string& file_name);
};
struct spek_audio_properties
class AudioFile
{
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;
public:
virtual ~AudioFile() {}
virtual void start(int samples) = 0;
virtual int read() = 0;
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;
};
// Initialise FFmpeg, should be called once on start up.
void spek_audio_init();
enum class AudioError
{
OK,
CANNOT_OPEN_FILE,
NO_STREAMS,
NO_AUDIO,
NO_DECODER,
NO_DURATION,
NO_CHANNELS,
CANNOT_OPEN_DECODER,
BAD_SAMPLE_FORMAT,
};
// 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);
const struct spek_audio_properties * spek_audio_get_properties(struct spek_audio_context *cs);
// Prepare the context for reading audio samples.
void spek_audio_start(struct spek_audio_context *cx, int samples);
// 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);
// 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

View File

@ -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;

View File

@ -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());
}
free(p);
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()
));
}
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;

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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"));
}

View File

@ -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,