spek

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

commit 9d12bd49fe8bbdf835c958832c869d0133dd9ad3
parent d9177f7d238405adc2a17594769ac9bdf208e600
Author: Alexander Kojevnikov <alexander@kojevnikov.com>
Date:   Tue, 14 Aug 2012 20:45:45 -0700

spek_audio_desc

Diffstat:
Msrc/Makefile.am | 2++
Asrc/spek-audio-desc.cc | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-audio-desc.hh | 28++++++++++++++++++++++++++++
Msrc/spek-audio.c | 112+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/spek-audio.h | 39+++++++++++++++++++--------------------
Msrc/spek-pipeline.vala | 33++-------------------------------
6 files changed, 213 insertions(+), 96 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am @@ -4,6 +4,8 @@ spek_SOURCES = \ spek.cc \ spek-audio.c \ spek-audio.h \ + spek-audio-desc.cc \ + spek-audio-desc.hh \ spek-fft.c \ spek-fft.h \ spek-platform.cc \ diff --git a/src/spek-audio-desc.cc b/src/spek-audio-desc.cc @@ -0,0 +1,95 @@ +/* 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.hh" + +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( + wxPLURAL("%d bit", "%d bits", properties->bits_per_sample), + properties->bits_per_sample + )); + } + if (properties->channels) { + items.Add(wxString::Format( + wxPLURAL("%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; + } + + // 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.hh b/src/spek-audio-desc.hh @@ -0,0 +1,28 @@ +/* spek-audio-desc.hh + * + * 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_HH_ +#define SPEK_AUDIO_DESC_HH_ + +#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.c b/src/spek-audio.c @@ -26,8 +26,22 @@ #include "spek-audio.h" -// TODO: move translations to UI code, return an error code instead. -#define _ +struct spek_audio_context +{ + char *file_name; // TODO: needed? + char *short_name; + AVFormatContext *format_context; + int audio_stream; + AVCodecContext *codec_context; + AVStream *stream; + AVCodec *codec; + int buffer_size; + AVPacket *packet; + int offset; + + struct spek_audio_properties properties; +}; + void spek_audio_init() { @@ -35,26 +49,33 @@ void spek_audio_init() av_register_all(); } -struct spek_audio_context * spek_audio_open(const char *file_name) +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) { - struct spek_audio_context *cx = malloc(sizeof(struct spek_audio_context)); - cx->file_name = strdup(file_name); + // TODO: malloc and initialise explicitely + struct spek_audio_context *cx = calloc(1, sizeof(struct spek_audio_context)); + cx->file_name = strdup(path); // av_open_input_file() cannot open files with Unicode chars in it // when running under Windows. When this happens we will re-try // using the corresponding short file name. - cx->short_name = spek_platform_short_path(file_name); + // TODO: test if it's already fixed in FFmpeg + cx->short_name = spek_platform_short_path(path); - if (avformat_open_input(&cx->format_context, file_name, NULL, NULL) != 0) { + if (avformat_open_input(&cx->format_context, path, NULL, NULL) != 0) { if (!cx->short_name || avformat_open_input(&cx->format_context, cx->short_name, NULL, NULL) != 0 ) { - cx->error = _("Cannot open input file"); + cx->properties.error = SPEK_AUDIO_CANNOT_OPEN_FILE; return cx; } } if (avformat_find_stream_info(cx->format_context, NULL) < 0) { // 24-bit APE returns an error but parses the stream info just fine. if (cx->format_context->nb_streams <= 0) { - cx->error = _("Cannot find stream info"); + cx->properties.error = SPEK_AUDIO_NO_STREAMS; return cx; } } @@ -66,65 +87,65 @@ struct spek_audio_context * spek_audio_open(const char *file_name) } } if (cx->audio_stream == -1) { - cx->error = _("The file contains no audio streams"); + cx->properties.error = SPEK_AUDIO_NO_AUDIO; return cx; } 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->error = _("Cannot find decoder"); + cx->properties.error = SPEK_AUDIO_NO_DECODER; return cx; } // We can already fill in the stream info even if the codec won't be able to open it. - cx->codec_name = strdup(cx->codec->long_name); - cx->bit_rate = cx->codec_context->bit_rate; - cx->sample_rate = cx->codec_context->sample_rate; - cx->bits_per_sample = cx->codec_context->bits_per_raw_sample; - if (!cx->bits_per_sample) { + 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->bits_per_sample = cx->codec_context->bits_per_coded_sample; + cx->properties.bits_per_sample = cx->codec_context->bits_per_coded_sample; } - cx->channels = cx->codec_context->channels; + cx->properties.channels = cx->codec_context->channels; if (cx->stream->duration != AV_NOPTS_VALUE) { - cx->duration = cx->stream->duration * av_q2d(cx->stream->time_base); + cx->properties.duration = cx->stream->duration * av_q2d(cx->stream->time_base); } else if (cx->format_context->duration != AV_NOPTS_VALUE) { - cx->duration = cx->format_context->duration / (double) AV_TIME_BASE; + cx->properties.duration = cx->format_context->duration / (double) AV_TIME_BASE; } else { - cx->error = _("Unknown duration"); + cx->properties.error = SPEK_AUDIO_NO_DURATION; return cx; } - if (cx->channels <= 0) { - cx->error = _("No audio channels"); + if (cx->properties.channels <= 0) { + cx->properties.error = SPEK_AUDIO_NO_CHANNELS; return cx; } if (avcodec_open2(cx->codec_context, cx->codec, NULL) < 0) { - cx->error = _("Cannot open decoder"); + cx->properties.error = SPEK_AUDIO_CANNOT_OPEN_DECODER; return cx; } switch (cx->codec_context->sample_fmt) { case SAMPLE_FMT_S16: - cx->width = 16; - cx->fp = false; + cx->properties.width = 16; + cx->properties.fp = false; break; case SAMPLE_FMT_S32: - cx->width = 32; - cx->fp = false; + cx->properties.width = 32; + cx->properties.fp = false; break; case SAMPLE_FMT_FLT: - cx->width = 32; - cx->fp = true; + cx->properties.width = 32; + cx->properties.fp = true; break; case SAMPLE_FMT_DBL: - cx->width = 64; - cx->fp = true; + cx->properties.width = 64; + cx->properties.fp = true; break; default: - cx->error = _("Unsupported sample format"); + cx->properties.error = SPEK_AUDIO_BAD_SAMPLE_FORMAT; return cx; } cx->buffer_size = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2; - cx->buffer = av_malloc(cx->buffer_size); + cx->properties.buffer = av_malloc(cx->buffer_size); cx->packet = av_mallocz(sizeof(AVPacket)); av_init_packet(cx->packet); cx->offset = 0; @@ -133,16 +154,17 @@ struct spek_audio_context * spek_audio_open(const char *file_name) void spek_audio_start(struct spek_audio_context *cx, int samples) { - int64_t rate = cx->sample_rate * (int64_t) cx->stream->time_base.num; + int64_t rate = cx->properties.sample_rate * (int64_t) cx->stream->time_base.num; int64_t duration = (int64_t) - (cx->duration * cx->stream->time_base.den / cx->stream->time_base.num); - cx->error_base = samples * (int64_t)cx->stream->time_base.den; - cx->frames_per_interval = av_rescale_rnd(duration, rate, cx->error_base, AV_ROUND_DOWN); - cx->error_per_interval = (duration * rate) % cx->error_base; + (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; } int spek_audio_read(struct spek_audio_context *cx) { - if (cx->error) { + if (cx->properties.error) { return -1; } @@ -150,7 +172,7 @@ int spek_audio_read(struct spek_audio_context *cx) { while (cx->packet->size > 0) { int buffer_size = cx->buffer_size; int len = avcodec_decode_audio3( - cx->codec_context, (int16_t *)cx->buffer, &buffer_size, cx->packet); + cx->codec_context, (int16_t *)cx->properties.buffer, &buffer_size, cx->packet); if (len < 0) { // Error, skip the frame. cx->packet->size = 0; @@ -195,11 +217,11 @@ void spek_audio_close (struct spek_audio_context *cx) if (cx->short_name != NULL) { free(cx->short_name); } - if (cx->codec_name != NULL) { - free(cx->codec_name); + if (cx->properties.codec_name != NULL) { + free(cx->properties.codec_name); } - if (cx->buffer) { - av_free(cx->buffer); + if (cx->properties.buffer) { + av_free(cx->properties.buffer); } if (cx->packet) { if (cx->packet->data) { diff --git a/src/spek-audio.h b/src/spek-audio.h @@ -26,29 +26,25 @@ extern "C" { #include <stdbool.h> #include <stdint.h> -struct AVFormatContext; -struct AVCodecContext; -struct AVStream; -struct AVCodec; -struct AVPacket; +struct spek_audio_context; -struct spek_audio_context +enum spek_audio_error { - // Internal data. - char *short_name; - AVFormatContext *format_context; - int audio_stream; - AVCodecContext *codec_context; - AVStream *stream; - AVCodec *codec; - int buffer_size; - AVPacket *packet; - int offset; + 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, +}; - // Exposed properties. - char *file_name; +struct spek_audio_properties +{ char *codec_name; - char *error; + enum spek_audio_error error; int bit_rate; int sample_rate; int bits_per_sample; @@ -56,6 +52,7 @@ struct spek_audio_context 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; @@ -67,7 +64,9 @@ 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 *file_name); +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); diff --git a/src/spek-pipeline.vala b/src/spek-pipeline.vala @@ -25,9 +25,6 @@ namespace Spek { public class Pipeline { - public string description { get; private set; } - public int sample_rate { get; private set; } - public double duration { get { return cx.duration; } } public delegate void Callback (int sample, float[] values); private Audio.Context cx; @@ -61,33 +58,7 @@ namespace Spek { this.threshold = threshold; this.cb = cb; - // Build the description string. - string[] items = {}; - if (cx.codec_name != null) { - items += cx.codec_name; - } - if (cx.bit_rate != 0) { - items += _("%d kbps").printf ((cx.bit_rate + 500) / 1000); - } - if (cx.sample_rate != 0) { - items += _("%d Hz").printf (cx.sample_rate); - } - // Show bits per sample only if there is no bitrate. - if (cx.bits_per_sample != 0 && cx.bit_rate == 0) { - items += ngettext ( - "%d bit", "%d bits", cx.bits_per_sample).printf (cx.bits_per_sample); - } - if (cx.channels != 0) { - items += ngettext ("%d channel", "%d channels", cx.channels). - printf (cx.channels); - } - description = items.length > 0 ? string.joinv (", ", items) : ""; - - if (cx.error != null) { - // TRANSLATORS: first %s is the error message, second %s is stream description. - description = _("%s: %s").printf (cx.error, description); - } else { - this.sample_rate = cx.sample_rate; + if (!cx.error) { this.nfft = 2 * bands - 2; this.coss = new float[nfft]; float cf = 2f * (float) Math.PI / this.nfft; @@ -109,7 +80,7 @@ namespace Spek { public void start () { stop (); - if (cx.error != null) return; + if (!cx.error) return; input_pos = 0; reader_mutex = new Mutex ();