spek_audio_desc

This commit is contained in:
Alexander Kojevnikov 2012-08-14 20:45:45 -07:00
parent d9177f7d23
commit 9d12bd49fe
6 changed files with 213 additions and 96 deletions

View File

@ -4,6 +4,8 @@ spek_SOURCES = \
spek.cc \ spek.cc \
spek-audio.c \ spek-audio.c \
spek-audio.h \ spek-audio.h \
spek-audio-desc.cc \
spek-audio-desc.hh \
spek-fft.c \ spek-fft.c \
spek-fft.h \ spek-fft.h \
spek-platform.cc \ spek-platform.cc \

95
src/spek-audio-desc.cc Normal file
View File

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

28
src/spek-audio-desc.hh Normal file
View File

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

View File

@ -26,8 +26,22 @@
#include "spek-audio.h" #include "spek-audio.h"
// TODO: move translations to UI code, return an error code instead. struct spek_audio_context
#define _ {
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() void spek_audio_init()
{ {
@ -35,26 +49,33 @@ void spek_audio_init()
av_register_all(); 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)
{ {
struct spek_audio_context *cx = malloc(sizeof(struct spek_audio_context)); return &cx->properties;
cx->file_name = strdup(file_name); }
struct spek_audio_context * spek_audio_open(const char *path)
{
// 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 // av_open_input_file() cannot open files with Unicode chars in it
// when running under Windows. When this happens we will re-try // when running under Windows. When this happens we will re-try
// using the corresponding short file name. // 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 || if (!cx->short_name ||
avformat_open_input(&cx->format_context, cx->short_name, NULL, NULL) != 0 ) { 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; return cx;
} }
} }
if (avformat_find_stream_info(cx->format_context, NULL) < 0) { if (avformat_find_stream_info(cx->format_context, NULL) < 0) {
// 24-bit APE returns an error but parses the stream info just fine. // 24-bit APE returns an error but parses the stream info just fine.
if (cx->format_context->nb_streams <= 0) { if (cx->format_context->nb_streams <= 0) {
cx->error = _("Cannot find stream info"); cx->properties.error = SPEK_AUDIO_NO_STREAMS;
return cx; return cx;
} }
} }
@ -66,65 +87,65 @@ struct spek_audio_context * spek_audio_open(const char *file_name)
} }
} }
if (cx->audio_stream == -1) { if (cx->audio_stream == -1) {
cx->error = _("The file contains no audio streams"); cx->properties.error = SPEK_AUDIO_NO_AUDIO;
return cx; return cx;
} }
cx->stream = cx->format_context->streams[cx->audio_stream]; cx->stream = cx->format_context->streams[cx->audio_stream];
cx->codec_context = cx->stream->codec; cx->codec_context = cx->stream->codec;
cx->codec = avcodec_find_decoder(cx->codec_context->codec_id); cx->codec = avcodec_find_decoder(cx->codec_context->codec_id);
if (cx->codec == NULL) { if (cx->codec == NULL) {
cx->error = _("Cannot find decoder"); cx->properties.error = SPEK_AUDIO_NO_DECODER;
return cx; return cx;
} }
// We can already fill in the stream info even if the codec won't be able to open it. // 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->properties.codec_name = strdup(cx->codec->long_name);
cx->bit_rate = cx->codec_context->bit_rate; cx->properties.bit_rate = cx->codec_context->bit_rate;
cx->sample_rate = cx->codec_context->sample_rate; cx->properties.sample_rate = cx->codec_context->sample_rate;
cx->bits_per_sample = cx->codec_context->bits_per_raw_sample; cx->properties.bits_per_sample = cx->codec_context->bits_per_raw_sample;
if (!cx->bits_per_sample) { if (!cx->properties.bits_per_sample) {
// APE uses bpcs, FLAC uses bprs. // 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) { 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) { } 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 { } else {
cx->error = _("Unknown duration"); cx->properties.error = SPEK_AUDIO_NO_DURATION;
return cx; return cx;
} }
if (cx->channels <= 0) { if (cx->properties.channels <= 0) {
cx->error = _("No audio channels"); cx->properties.error = SPEK_AUDIO_NO_CHANNELS;
return cx; return cx;
} }
if (avcodec_open2(cx->codec_context, cx->codec, NULL) < 0) { 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; return cx;
} }
switch (cx->codec_context->sample_fmt) { switch (cx->codec_context->sample_fmt) {
case SAMPLE_FMT_S16: case SAMPLE_FMT_S16:
cx->width = 16; cx->properties.width = 16;
cx->fp = false; cx->properties.fp = false;
break; break;
case SAMPLE_FMT_S32: case SAMPLE_FMT_S32:
cx->width = 32; cx->properties.width = 32;
cx->fp = false; cx->properties.fp = false;
break; break;
case SAMPLE_FMT_FLT: case SAMPLE_FMT_FLT:
cx->width = 32; cx->properties.width = 32;
cx->fp = true; cx->properties.fp = true;
break; break;
case SAMPLE_FMT_DBL: case SAMPLE_FMT_DBL:
cx->width = 64; cx->properties.width = 64;
cx->fp = true; cx->properties.fp = true;
break; break;
default: default:
cx->error = _("Unsupported sample format"); cx->properties.error = SPEK_AUDIO_BAD_SAMPLE_FORMAT;
return cx; return cx;
} }
cx->buffer_size = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2; 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)); cx->packet = av_mallocz(sizeof(AVPacket));
av_init_packet(cx->packet); av_init_packet(cx->packet);
cx->offset = 0; 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) 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) int64_t duration = (int64_t)
(cx->duration * cx->stream->time_base.den / cx->stream->time_base.num); (cx->properties.duration * cx->stream->time_base.den / cx->stream->time_base.num);
cx->error_base = samples * (int64_t)cx->stream->time_base.den; cx->properties.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->properties.frames_per_interval = av_rescale_rnd(
cx->error_per_interval = (duration * rate) % cx->error_base; 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) { int spek_audio_read(struct spek_audio_context *cx) {
if (cx->error) { if (cx->properties.error) {
return -1; return -1;
} }
@ -150,7 +172,7 @@ int spek_audio_read(struct spek_audio_context *cx) {
while (cx->packet->size > 0) { while (cx->packet->size > 0) {
int buffer_size = cx->buffer_size; int buffer_size = cx->buffer_size;
int len = avcodec_decode_audio3( 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) { if (len < 0) {
// Error, skip the frame. // Error, skip the frame.
cx->packet->size = 0; cx->packet->size = 0;
@ -195,11 +217,11 @@ void spek_audio_close (struct spek_audio_context *cx)
if (cx->short_name != NULL) { if (cx->short_name != NULL) {
free(cx->short_name); free(cx->short_name);
} }
if (cx->codec_name != NULL) { if (cx->properties.codec_name != NULL) {
free(cx->codec_name); free(cx->properties.codec_name);
} }
if (cx->buffer) { if (cx->properties.buffer) {
av_free(cx->buffer); av_free(cx->properties.buffer);
} }
if (cx->packet) { if (cx->packet) {
if (cx->packet->data) { if (cx->packet->data) {

View File

@ -26,29 +26,25 @@ extern "C" {
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
struct AVFormatContext; struct spek_audio_context;
struct AVCodecContext;
struct AVStream;
struct AVCodec;
struct AVPacket;
struct spek_audio_context enum spek_audio_error
{ {
// Internal data. SPEK_AUDIO_OK = 0,
char *short_name; SPEK_AUDIO_CANNOT_OPEN_FILE,
AVFormatContext *format_context; SPEK_AUDIO_NO_STREAMS,
int audio_stream; SPEK_AUDIO_NO_AUDIO,
AVCodecContext *codec_context; SPEK_AUDIO_NO_DECODER,
AVStream *stream; SPEK_AUDIO_NO_DURATION,
AVCodec *codec; SPEK_AUDIO_NO_CHANNELS,
int buffer_size; SPEK_AUDIO_CANNOT_OPEN_DECODER,
AVPacket *packet; SPEK_AUDIO_BAD_SAMPLE_FORMAT,
int offset; };
// Exposed properties. struct spek_audio_properties
char *file_name; {
char *codec_name; char *codec_name;
char *error; enum spek_audio_error error;
int bit_rate; int bit_rate;
int sample_rate; int sample_rate;
int bits_per_sample; int bits_per_sample;
@ -56,6 +52,7 @@ struct spek_audio_context
bool fp; // floating-point sample representation bool fp; // floating-point sample representation
int channels; int channels;
double duration; double duration;
// TODO: these four guys don't belong here, move them somewhere else when revamping the pipeline
uint8_t *buffer; uint8_t *buffer;
int64_t frames_per_interval; int64_t frames_per_interval;
int64_t error_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. // Open the file, check if it has an audio stream which can be decoded.
// On error, initialises the `error` field in the returned context. // 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. // Prepare the context for reading audio samples.
void spek_audio_start(struct spek_audio_context *cx, int samples); void spek_audio_start(struct spek_audio_context *cx, int samples);

View File

@ -25,9 +25,6 @@
namespace Spek { namespace Spek {
public class Pipeline { 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); public delegate void Callback (int sample, float[] values);
private Audio.Context cx; private Audio.Context cx;
@ -61,33 +58,7 @@ namespace Spek {
this.threshold = threshold; this.threshold = threshold;
this.cb = cb; this.cb = cb;
// Build the description string. if (!cx.error) {
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;
this.nfft = 2 * bands - 2; this.nfft = 2 * bands - 2;
this.coss = new float[nfft]; this.coss = new float[nfft];
float cf = 2f * (float) Math.PI / this.nfft; float cf = 2f * (float) Math.PI / this.nfft;
@ -109,7 +80,7 @@ namespace Spek {
public void start () { public void start () {
stop (); stop ();
if (cx.error != null) return; if (!cx.error) return;
input_pos = 0; input_pos = 0;
reader_mutex = new Mutex (); reader_mutex = new Mutex ();