mirror of
https://github.com/alexkay/spek.git
synced 2025-04-17 17:12:19 +03:00
spek_audio_desc
This commit is contained in:
parent
d9177f7d23
commit
9d12bd49fe
@ -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
95
src/spek-audio-desc.cc
Normal 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
28
src/spek-audio-desc.hh
Normal 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
|
112
src/spek-audio.c
112
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)
|
||||
{
|
||||
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) {
|
||||
|
@ -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);
|
||||
|
@ -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 ();
|
||||
|
Loading…
x
Reference in New Issue
Block a user