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-audio.c \
spek-audio.h \
spek-audio-desc.cc \
spek-audio-desc.hh \
spek-fft.c \
spek-fft.h \
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"
// 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)
{
struct spek_audio_context *cx = malloc(sizeof(struct spek_audio_context));
cx->file_name = strdup(file_name);
return &cx->properties;
}
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
// 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) {

View File

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

View File

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