From 8691117503a73357e70b20a8b587c0f5a62229d4 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 9 Nov 2017 23:03:27 +0000 Subject: [PATCH] Go back to old audiofile plugin from 1.x release This adds back audio_decoder library as module as well. Likely breaks bridges build, to be handled soon. --- Makefile | 1 + source/Makefile.mk | 20 + source/backend/CarlaUtils.cpp | 24 +- source/backend/Makefile | 3 +- source/bridges-plugin/Makefile | 4 + source/modules/Makefile | 2 + source/modules/audio_decoder/Makefile | 61 +++ source/modules/audio_decoder/ad.h | 127 +++++++ source/modules/audio_decoder/ad_ffmpeg.c | 387 ++++++++++++++++++++ source/modules/audio_decoder/ad_plugin.c | 175 +++++++++ source/modules/audio_decoder/ad_plugin.h | 64 ++++ source/modules/audio_decoder/ad_soundfile.c | 158 ++++++++ source/modules/audio_decoder/ffcompat.h | 95 +++++ source/native-plugins/audio-base.hpp | 387 ++++++++++++++++++++ source/native-plugins/audio-file.cpp | 214 +++++------ source/plugin/Makefile | 18 +- 16 files changed, 1585 insertions(+), 155 deletions(-) create mode 100644 source/modules/audio_decoder/Makefile create mode 100644 source/modules/audio_decoder/ad.h create mode 100644 source/modules/audio_decoder/ad_ffmpeg.c create mode 100644 source/modules/audio_decoder/ad_plugin.c create mode 100644 source/modules/audio_decoder/ad_plugin.h create mode 100644 source/modules/audio_decoder/ad_soundfile.c create mode 100644 source/modules/audio_decoder/ffcompat.h create mode 100644 source/native-plugins/audio-base.hpp diff --git a/Makefile b/Makefile index 0a4f17aa9..f6e67fd30 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,7 @@ ALL_LIBS += $(MODULEDIR)/carla_engine_plugin.a ALL_LIBS += $(MODULEDIR)/carla_plugin.a ALL_LIBS += $(MODULEDIR)/jackbridge.a ALL_LIBS += $(MODULEDIR)/native-plugins.a +ALL_LIBS += $(MODULEDIR)/audio_decoder.a ALL_LIBS += $(MODULEDIR)/lilv.a ALL_LIBS += $(MODULEDIR)/rtmempool.a ALL_LIBS += $(MODULEDIR)/water.a diff --git a/source/Makefile.mk b/source/Makefile.mk index ded03a2ce..25037d21d 100644 --- a/source/Makefile.mk +++ b/source/Makefile.mk @@ -183,9 +183,11 @@ HAVE_ALSA = $(shell pkg-config --exists alsa && echo true) HAVE_HYLIA = true endif +HAVE_FFMPEG = $(shell pkg-config --exists libavcodec libavformat libavutil && echo true) HAVE_FLUIDSYNTH = $(shell pkg-config --exists fluidsynth && echo true) HAVE_LIBLO = $(shell pkg-config --exists liblo && echo true) HAVE_LINUXSAMPLER = $(shell pkg-config --atleast-version=1.0.0.svn41 linuxsampler && echo true) +HAVE_SNDFILE = $(shell pkg-config --exists sndfile && echo true) # -------------------------------------------------------------- # Check for optional libs (special non-pkgconfig unix tests) @@ -281,6 +283,10 @@ ifeq ($(HAVE_FLUIDSYNTH),true) BASE_FLAGS += -DHAVE_FLUIDSYNTH endif +ifeq ($(HAVE_FFMPEG),true) +BASE_FLAGS += -DHAVE_FFMPEG +endif + ifeq ($(HAVE_HYLIA),true) BASE_FLAGS += -DHAVE_HYLIA endif @@ -297,6 +303,10 @@ ifeq ($(HAVE_LINUXSAMPLER),true) BASE_FLAGS += -DHAVE_LINUXSAMPLER endif +ifeq ($(HAVE_SNDFILE),true) +BASE_FLAGS += -DHAVE_SNDFILE +endif + ifeq ($(HAVE_X11),true) BASE_FLAGS += -DHAVE_X11 endif @@ -309,6 +319,11 @@ LIBLO_FLAGS = $(shell pkg-config --cflags liblo) LIBLO_LIBS = $(shell pkg-config --libs liblo) endif +ifeq ($(HAVE_FFMPEG),true) +FFMPEG_FLAGS = $(shell pkg-config --cflags libavcodec libavformat libavutil) +FFMPEG_LIBS = $(shell pkg-config --libs libavcodec libavformat libavutil) +endif + ifeq ($(HAVE_FLUIDSYNTH),true) FLUIDSYNTH_FLAGS = $(shell pkg-config --cflags fluidsynth) FLUIDSYNTH_LIBS = $(shell pkg-config --libs fluidsynth) @@ -325,6 +340,11 @@ LINUXSAMPLER_LIBS += -lws2_32 endif endif +ifeq ($(HAVE_SNDFILE),true) +SNDFILE_FLAGS = $(shell pkg-config --cflags sndfile) +SNDFILE_LIBS = $(shell pkg-config --libs sndfile) +endif + ifeq ($(HAVE_X11),true) X11_FLAGS = $(shell pkg-config --cflags x11) X11_LIBS = $(shell pkg-config --libs x11) diff --git a/source/backend/CarlaUtils.cpp b/source/backend/CarlaUtils.cpp index aedd456a5..5a7a98a26 100644 --- a/source/backend/CarlaUtils.cpp +++ b/source/backend/CarlaUtils.cpp @@ -512,26 +512,12 @@ const char* carla_get_supported_file_extensions() #endif ; -#if 0 // Audio files - { - using namespace water; - - AudioFormatManager afm; - afm.registerBasicFormats(); - - String waterFormats; - - for (AudioFormat **it=afm.begin(), **end=afm.end(); it != end; ++it) - { - const StringArray& exts((*it)->getFileExtensions()); - - for (String *eit=exts.begin(), *eend=exts.end(); eit != eend; ++eit) - waterFormats += String(";*" + (*eit)).toRawUTF8(); - } - - retText += waterFormats.toRawUTF8(); - } +#ifdef HAVE_SNDFILE + retText += ";*.aiff;*.flac;*.oga;*.ogg;*.w64;*.wav"; +#endif +#ifdef HAVE_FFMPEG + retText += ";*.3g2;*.3gp;*.aac;*.ac3;*.amr;*.ape;*.mp2;*.mp3;*.mpc;*.wma"; #endif } diff --git a/source/backend/Makefile b/source/backend/Makefile index 17c83adff..b331475ec 100644 --- a/source/backend/Makefile +++ b/source/backend/Makefile @@ -24,8 +24,9 @@ TARGETS = \ STANDALONE_LIBS = $(MODULEDIR)/carla_engine.a STANDALONE_LIBS += $(MODULEDIR)/carla_plugin.a - STANDALONE_LIBS += $(MODULEDIR)/jackbridge.a + +STANDALONE_LIBS += $(MODULEDIR)/audio_decoder.a STANDALONE_LIBS += $(MODULEDIR)/lilv.a STANDALONE_LIBS += $(MODULEDIR)/native-plugins.a STANDALONE_LIBS += $(MODULEDIR)/rtmempool.a diff --git a/source/bridges-plugin/Makefile b/source/bridges-plugin/Makefile index 19a113d41..02eeba5b6 100644 --- a/source/bridges-plugin/Makefile +++ b/source/bridges-plugin/Makefile @@ -43,6 +43,10 @@ LIBS_win64 = $(MODULEDIR)/jackbridge.win64e.a endif LINK_FLAGS += $(JACKBRIDGE_LIBS) +LIBS_native += $(MODULEDIR)/audio_decoder.a +LINK_FLAGS += $(FFMPEG_LIBS) +LINK_FLAGS += $(SNDFILE_LIBS) + LIBS_native += $(MODULEDIR)/lilv.a LIBS_posix32 += $(MODULEDIR)/lilv.posix32.a LIBS_posix64 += $(MODULEDIR)/lilv.posix64.a diff --git a/source/modules/Makefile b/source/modules/Makefile index 5267cf5d6..d59c3a527 100644 --- a/source/modules/Makefile +++ b/source/modules/Makefile @@ -9,6 +9,8 @@ all: clean: + $(MAKE) clean -C audio_decoder + $(MAKE) clean -C dgl $(MAKE) clean -C lilv $(MAKE) clean -C rtaudio $(MAKE) clean -C rtmempool diff --git a/source/modules/audio_decoder/Makefile b/source/modules/audio_decoder/Makefile new file mode 100644 index 000000000..e4712d86a --- /dev/null +++ b/source/modules/audio_decoder/Makefile @@ -0,0 +1,61 @@ +#!/usr/bin/make -f +# Makefile for audio_decoder # +# -------------------------- # +# Created by falkTX +# + +CWD=../.. +MODULENAME=audio_decoder +include ../Makefile.mk + +# ---------------------------------------------------------------------------------------------------------------------------- + +# BUILD_C_FLAGS += + +# ---------------------------------------------------------------------------------------------------------------------------- + +OBJS = \ + $(OBJDIR)/ad_ffmpeg.c.o \ + $(OBJDIR)/ad_plugin.c.o \ + $(OBJDIR)/ad_soundfile.c.o + +# ---------------------------------------------------------------------------------------------------------------------------- + +all: $(MODULEDIR)/$(MODULENAME).a + +# ---------------------------------------------------------------------------------------------------------------------------- + +clean: + rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a + +debug: + $(MAKE) DEBUG=true + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(MODULEDIR)/$(MODULENAME).a: $(OBJS) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).a" + @rm -f $@ + @$(AR) crs $@ $^ + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(OBJDIR)/ad_ffmpeg.c.o: ad_ffmpeg.c + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CC) $< $(BUILD_C_FLAGS) $(FFMPEG_FLAGS) -c -o $@ + +$(OBJDIR)/ad_plugin.c.o: ad_plugin.c + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CC) $< $(BUILD_C_FLAGS) -c -o $@ + +$(OBJDIR)/ad_soundfile.c.o: ad_soundfile.c + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CC) $< $(BUILD_C_FLAGS) $(SNDFILE_FLAGS) -c -o $@ + +-include $(OBJS:%.o=%.d) + +# ---------------------------------------------------------------------------------------------------------------------------- diff --git a/source/modules/audio_decoder/ad.h b/source/modules/audio_decoder/ad.h new file mode 100644 index 000000000..a0169763d --- /dev/null +++ b/source/modules/audio_decoder/ad.h @@ -0,0 +1,127 @@ +/** + @brief audio-decoder - wrapper around libsndfile and libav* + @file ad.h + @author Robin Gareus + + Copyright (C) 2011-2013 Robin Gareus + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser Public License as published by + the Free Software Foundation; either version 2.1, or (at your option) + any later version. + + This program 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 Lesser Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#ifndef __AD_H__ +#define __AD_H__ + +#include +#include + +struct adinfo { + unsigned int sample_rate; + unsigned int channels; + int64_t length; //milliseconds + int64_t frames; //total number of frames (eg a frame for 16bit stereo is 4 bytes). + int bit_rate; + int bit_depth; + char * meta_data; +}; + +/* global init function - register codecs */ +void ad_init(); + +/* --- public API --- */ + +/** open an audio file + * @param fn file-name + * @param nfo pointer to a adinfo struct which will hold information about the file. + * @return NULL on error, a pointer to an opaque soundfile-decoder object on success. + */ +void * ad_open (const char *fn, struct adinfo *nfo); + +/** close an audio file and release decoder structures + * @param sf decoder handle + * @return 0 on succees, -1 if sf was invalid or not open (return value can usually be ignored) + */ +int ad_close (void *sf); + +/** seel to a given position in the file + * @param sf decoder handle + * @param pos frame position to seek to in frames (1 frame = number-of-channel samples) from the start of the file. + * @return the current position in frames (multi-channel samples) from the start of the file. On error this function returns -1. + */ +int64_t ad_seek (void *sf, int64_t pos); + +/** decode audio data chunk to raw interleaved channel floating point data + * + * @param sf decoder handle + * @param out place to store data -- must be large enough to hold (sizeof(float) * len) bytes. + * @param len number of samples (!) to read (should be a multiple of nfo->channels). + * @return the number of read samples. + */ +ssize_t ad_read (void *sf, float* out, size_t len); + +/** re-read the file information and meta-data. + * + * this is not neccesary in general \ref ad_open includes an inplicit call + * but meta-data may change in live-stream in which case en explicit call to + * ad_into is needed to update the inforation + * + * @param fn file-name + * @param nfo pointer to a adinfo struct which will hold information about the file. + * @return 0 on succees, -1 if sf was invalid or not open + */ +int ad_info (void *sf, struct adinfo *nfo); + +/** zero initialize the information struct. * (does not free nfo->meta_data) + * @param nfo pointer to a adinfo struct + */ +void ad_clear_nfo (struct adinfo *nfo); + +/** free possibly allocated meta-data text + * @param nfo pointer to a adinfo struct + */ +void ad_free_nfo (struct adinfo *nfo); + + +/* --- helper functions --- */ + +/** read file info + * combines ad_open() and ad_close() + */ +int ad_finfo (const char *, struct adinfo *); + +/** + * wrapper around \ref ad_read, downmixes all channels to mono + */ +ssize_t ad_read_mono_dbl (void *, struct adinfo *, double*, size_t); + +/** + * calls dbg() to print file info to stderr. + * + * @param dbglvl + * @param nfo + */ +void ad_dump_nfo (int dbglvl, struct adinfo *nfo); + +/** set audio-decoder debug level -- all info is printed to stderr. + * + * @param lvl debug-level threshold + * -1: absolutley silent + * 0: errors only + * 1: errors + info + * 2: + debug + * 3: + low-level-debug info + */ +void ad_set_debuglevel(int lvl); + +#endif diff --git a/source/modules/audio_decoder/ad_ffmpeg.c b/source/modules/audio_decoder/ad_ffmpeg.c new file mode 100644 index 000000000..c6270f31e --- /dev/null +++ b/source/modules/audio_decoder/ad_ffmpeg.c @@ -0,0 +1,387 @@ +/** + Copyright (C) 2011-2013 Robin Gareus + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser Public License as published by + the Free Software Foundation; either version 2.1, or (at your option) + any later version. + + This program 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 Lesser Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#include +#include +#include +#include +#include + +#include "ad_plugin.h" + +#ifdef HAVE_FFMPEG + +#include "ffcompat.h" + +#ifndef MIN +#define MIN(a,b) ( ( (a) < (b) )? (a) : (b) ) +#endif + +typedef struct { + AVFormatContext* formatContext; + AVCodecContext* codecContext; + AVCodec* codec; + AVPacket packet; + int audioStream; + int pkt_len; + uint8_t* pkt_ptr; + + int16_t m_tmpBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE]; + int16_t* m_tmpBufferStart; + unsigned long m_tmpBufferLen; + + int64_t decoder_clock; + int64_t output_clock; + int64_t seek_frame; + unsigned int samplerate; + unsigned int channels; + int64_t length; +} ffmpeg_audio_decoder; + + +static int ad_info_ffmpeg(void *sf, struct adinfo *nfo) { + ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) sf; + if (!priv) return -1; + if (nfo) { + nfo->sample_rate = priv->samplerate; + nfo->channels = priv->channels; + nfo->frames = priv->length; + if (nfo->sample_rate==0) return -1; + nfo->length = (nfo->frames * 1000) / nfo->sample_rate; + nfo->bit_rate = priv->formatContext->bit_rate; + nfo->bit_depth = 0; + nfo->meta_data = NULL; + +#ifdef WITH_GTK // XXX replace g_* functions with POSIX equiv + AVDictionaryEntry *tag = NULL; + // Tags in container + while ((tag = av_dict_get(priv->formatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + dbg(2, "FTAG: %s=%s", tag->key, tag->value); + char * tmp = g_strdup_printf("%s%s%s:%s", nfo->meta_data?nfo->meta_data:"",nfo->meta_data?"\n":"", tag->key, tag->value); + if (nfo->meta_data) g_free(nfo->meta_data); + nfo->meta_data = tmp; + } + // Tags in stream + tag=NULL; + AVStream *stream = priv->formatContext->streams[priv->audioStream]; + while ((tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + dbg(2, "STAG: %s=%s", tag->key, tag->value); + char * tmp = g_strdup_printf("%s%s%s:%s", nfo->meta_data?nfo->meta_data:"",nfo->meta_data?"\n":"", tag->key, tag->value); + if (nfo->meta_data) g_free(nfo->meta_data); + nfo->meta_data = tmp; + } +#endif + } + return 0; +} + +static void *ad_open_ffmpeg(const char *fn, struct adinfo *nfo) { + ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) calloc(1, sizeof(ffmpeg_audio_decoder)); + + priv->m_tmpBufferStart=NULL; + priv->m_tmpBufferLen=0; + priv->decoder_clock=priv->output_clock=priv->seek_frame=0; + priv->packet.size=0; priv->packet.data=NULL; + + if (avformat_open_input(&priv->formatContext, fn, NULL, NULL) <0) { + dbg(0, "ffmpeg is unable to open file '%s'.", fn); + free(priv); return(NULL); + } + + if (avformat_find_stream_info(priv->formatContext, NULL) < 0) { + avformat_close_input(&priv->formatContext); + dbg(0, "av_find_stream_info failed" ); + free(priv); return(NULL); + } + + priv->audioStream = -1; + unsigned int i; + for (i=0; iformatContext->nb_streams; i++) { + if (priv->formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + priv->audioStream = i; + break; + } + } + if (priv->audioStream == -1) { + dbg(0, "No Audio Stream found in file"); + avformat_close_input(&priv->formatContext); + free(priv); return(NULL); + } + + priv->codecContext = priv->formatContext->streams[priv->audioStream]->codec; + priv->codec = avcodec_find_decoder(priv->codecContext->codec_id); + + if (priv->codec == NULL) { + avformat_close_input(&priv->formatContext); + dbg(0, "Codec not supported by ffmpeg"); + free(priv); return(NULL); + } + if (avcodec_open2(priv->codecContext, priv->codec, NULL) < 0) { + dbg(0, "avcodec_open failed" ); + free(priv); return(NULL); + } + + dbg(2, "ffmpeg - audio tics: %i/%i [sec]",priv->formatContext->streams[priv->audioStream]->time_base.num,priv->formatContext->streams[priv->audioStream]->time_base.den); + + int64_t len = priv->formatContext->duration - priv->formatContext->start_time; + + priv->formatContext->flags|=AVFMT_FLAG_GENPTS; + priv->formatContext->flags|=AVFMT_FLAG_IGNIDX; + + priv->samplerate = priv->codecContext->sample_rate; + priv->channels = priv->codecContext->channels ; + priv->length = (int64_t)( len * priv->samplerate / AV_TIME_BASE ); + + if (ad_info_ffmpeg((void*)priv, nfo)) { + dbg(0, "invalid file info (sample-rate==0)"); + free(priv); return(NULL); + } + + dbg(1, "ffmpeg - %s", fn); + if (nfo) + dbg(1, "ffmpeg - sr:%i c:%i d:%"PRIi64" f:%"PRIi64, nfo->sample_rate, nfo->channels, nfo->length, nfo->frames); + + return (void*) priv; +} + +static int ad_close_ffmpeg(void *sf) { + ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) sf; + if (!priv) return -1; + avcodec_close(priv->codecContext); + avformat_close_input(&priv->formatContext); + free(priv); + return 0; +} + +static void int16_to_float(int16_t *in, float *out, int num_channels, int num_samples, int out_offset) { + int i,ii; + for (i=0;ichannels; + + size_t written = 0; + ssize_t ret = 0; + while (ret >= 0 && written < frames) { + dbg(3,"loop: %i/%i (bl:%lu)",written, frames, priv->m_tmpBufferLen ); + if (priv->seek_frame == 0 && priv->m_tmpBufferLen > 0 ) { + int s = MIN(priv->m_tmpBufferLen / priv->channels, frames - written ); + int16_to_float(priv->m_tmpBufferStart, d, priv->channels, s , written); + written += s; + priv->output_clock+=s; + s = s * priv->channels; + priv->m_tmpBufferStart += s; + priv->m_tmpBufferLen -= s; + ret = 0; + } else { + priv->m_tmpBufferStart = priv->m_tmpBuffer; + priv->m_tmpBufferLen = 0; + + if (!priv->pkt_ptr || priv->pkt_len <1 ) { + if (priv->packet.data) av_free_packet(&priv->packet); + ret = av_read_frame(priv->formatContext, &priv->packet); + if (ret<0) { dbg(1, "reached end of file."); break; } + priv->pkt_len = priv->packet.size; + priv->pkt_ptr = priv->packet.data; + } + + if (priv->packet.stream_index != priv->audioStream) { + priv->pkt_ptr = NULL; + continue; + } + + /* decode all chunks in packet */ + int data_size = AVCODEC_MAX_AUDIO_FRAME_SIZE; + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 0, 0) + // TODO use av_frame_alloc() and av_frame_free() with newer ffmpeg + AVFrame avf; + memset(&avf, 0, sizeof(AVFrame)); + int got_frame = 0; + ret = avcodec_decode_audio4(priv->codecContext, &avf, &got_frame, &priv->packet); + if (ret >= 0 && got_frame) { + int ch, plane_size; + const int planar = av_sample_fmt_is_planar(priv->codecContext->sample_fmt); + data_size = av_samples_get_buffer_size(&plane_size, priv->codecContext->channels, avf.nb_samples, priv->codecContext->sample_fmt, 1); + if (data_size <= AVCODEC_MAX_AUDIO_FRAME_SIZE) { + memcpy(priv->m_tmpBuffer, avf.extended_data[0], plane_size); + if (planar && priv->codecContext->channels > 1) { + uint8_t *out = ((uint8_t *)priv->m_tmpBuffer) + plane_size; + for (ch = 1; ch < priv->codecContext->channels; ch++) { + memcpy(out, avf.extended_data[ch], plane_size); + out += plane_size; + } + } + } + } else { + ret = -1; + } +#elif LIBAVUTIL_VERSION_INT > AV_VERSION_INT(49, 15, 0) && LIBAVCODEC_VERSION_INT > AV_VERSION_INT(52, 20, 1) // ?? + // this was deprecated in LIBAVCODEC_VERSION_MAJOR 53 + ret = avcodec_decode_audio3(priv->codecContext, + priv->m_tmpBuffer, &data_size, &priv->packet); +#else + int len = priv->packet.size; + uint8_t *ptr = priv->packet.data; + ret = avcodec_decode_audio2(priv->codecContext, + priv->m_tmpBuffer, &data_size, ptr, len); +#endif + + if (ret < 0 || ret > priv->pkt_len) { +#if 0 + dbg(0, "audio decode error"); + return -1; +#endif + priv->pkt_len=0; + ret=0; + continue; + } + + priv->pkt_len -= ret; priv->pkt_ptr += ret; + + /* sample exact alignment */ + if (priv->packet.pts != AV_NOPTS_VALUE) { + priv->decoder_clock = priv->samplerate * av_q2d(priv->formatContext->streams[priv->audioStream]->time_base) * priv->packet.pts; + } else { + dbg(0, "!!! NO PTS timestamp in file"); + priv->decoder_clock += (data_size>>1) / priv->channels; + } + + if (data_size>0) { + priv->m_tmpBufferLen+= (data_size>>1); // 2 bytes per sample + } + + /* align buffer after seek. */ + if (priv->seek_frame > 0) { + const int diff = priv->output_clock-priv->decoder_clock; + if (diff<0) { + /* seek ended up past the wanted sample */ + dbg(0, " !!! Audio seek failed."); + return -1; + } else if (priv->m_tmpBufferLen < (diff*priv->channels)) { + /* wanted sample not in current buffer - keep going */ + dbg(2, " !!! seeked sample was not in decoded buffer. frames-to-go: %li", diff); + priv->m_tmpBufferLen = 0; + } else if (diff!=0 && data_size > 0) { + /* wanted sample is in current buffer but not at the beginnning */ + dbg(2, " !!! sync buffer to seek. (diff:%i)", diff); + priv->m_tmpBufferStart+= diff*priv->codecContext->channels; + priv->m_tmpBufferLen -= diff*priv->codecContext->channels; +#if 1 + memmove(priv->m_tmpBuffer, priv->m_tmpBufferStart, priv->m_tmpBufferLen); + priv->m_tmpBufferStart = priv->m_tmpBuffer; +#endif + priv->seek_frame=0; + priv->decoder_clock += diff; + } else if (data_size > 0) { + dbg(2, "Audio exact sync-seek (%"PRIi64" == %"PRIi64")", priv->decoder_clock, priv->seek_frame); + priv->seek_frame=0; + } else { + dbg(0, "Error: no audio data in packet"); + } + } + //dbg(0, "PTS: decoder:%"PRIi64". - want: %"PRIi64, priv->decoder_clock, priv->output_clock); + //dbg(0, "CLK: frame: %"PRIi64" T:%.3fs",priv->decoder_clock, (float) priv->decoder_clock/priv->samplerate); + } + } + if (written!=frames) { + dbg(2, "short-read"); + } + return written * priv->channels; +} + +static int64_t ad_seek_ffmpeg(void *sf, int64_t pos) { + ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) sf; + if (!sf) return -1; + if (pos == priv->output_clock) return pos; + + /* flush internal buffer */ + priv->m_tmpBufferLen = 0; + priv->seek_frame = pos; + priv->output_clock = pos; + priv->pkt_len = 0; priv->pkt_ptr = NULL; + priv->decoder_clock = 0; + +#if 0 + /* TODO seek at least 1 packet before target. + * for mpeg compressed files, the + * output may depend on past frames! */ + if (pos > 8192) pos -= 8192; + else pos = 0; +#endif + + const int64_t timestamp = pos / av_q2d(priv->formatContext->streams[priv->audioStream]->time_base) / priv->samplerate; + dbg(2, "seek frame:%"PRIi64" - idx:%"PRIi64, pos, timestamp); + + av_seek_frame(priv->formatContext, priv->audioStream, timestamp, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(priv->codecContext); + return pos; +} + +static int ad_eval_ffmpeg(const char *f) { + char *ext = strrchr(f, '.'); + if (!ext) return 10; + // libavformat.. guess_format.. + return 40; +} +#endif + +static const ad_plugin ad_ffmpeg = { +#ifdef HAVE_FFMPEG + &ad_eval_ffmpeg, + &ad_open_ffmpeg, + &ad_close_ffmpeg, + &ad_info_ffmpeg, + &ad_seek_ffmpeg, + &ad_read_ffmpeg +#else + &ad_eval_null, + &ad_open_null, + &ad_close_null, + &ad_info_null, + &ad_seek_null, + &ad_read_null +#endif +}; + +/* dlopen handler */ +const ad_plugin * adp_get_ffmpeg() { +#ifdef HAVE_FFMPEG + static int ffinit = 0; + if (!ffinit) { + ffinit=1; +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 5, 0) + avcodec_init(); +#endif + av_register_all(); + avcodec_register_all(); + if(ad_debug_level <= 1) + av_log_set_level(AV_LOG_QUIET); + else + av_log_set_level(AV_LOG_VERBOSE); + } +#endif + return &ad_ffmpeg; +} diff --git a/source/modules/audio_decoder/ad_plugin.c b/source/modules/audio_decoder/ad_plugin.c new file mode 100644 index 000000000..fe83325dc --- /dev/null +++ b/source/modules/audio_decoder/ad_plugin.c @@ -0,0 +1,175 @@ +/** + Copyright (C) 2011-2013 Robin Gareus + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser Public License as published by + the Free Software Foundation; either version 2.1, or (at your option) + any later version. + + This program 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 Lesser Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#include +#include +#include +#include +#include +#include + +#include "ad_plugin.h" + +int ad_debug_level = 0; + +#define UNUSED(x) (void)(x) + +int ad_eval_null(const char *f) { UNUSED(f); return -1; } +void * ad_open_null(const char *f, struct adinfo *n) { UNUSED(f); UNUSED(n); return NULL; } +int ad_close_null(void *x) { UNUSED(x); return -1; } +int ad_info_null(void *x, struct adinfo *n) { UNUSED(x); UNUSED(n); return -1; } +int64_t ad_seek_null(void *x, int64_t p) { UNUSED(x); UNUSED(p); return -1; } +ssize_t ad_read_null(void *x, float*d, size_t s) { UNUSED(x); UNUSED(d); UNUSED(s); return -1;} + +typedef struct { + ad_plugin const *b; ///< decoder back-end + void *d; ///< backend data +} adecoder; + +/* samplecat api */ + +void ad_init() { /* global init */ } + +static ad_plugin const * choose_backend(const char *fn) { + int max, val; + ad_plugin const *b=NULL; + max=0; + + val=adp_get_sndfile()->eval(fn); + if (val>max) {max=val; b=adp_get_sndfile();} + + val=adp_get_ffmpeg()->eval(fn); + if (val>max) {max=val; b=adp_get_ffmpeg();} + + return b; +} + +void *ad_open(const char *fn, struct adinfo *nfo) { + adecoder *d = (adecoder*) calloc(1, sizeof(adecoder)); + ad_clear_nfo(nfo); + + d->b = choose_backend(fn); + if (!d->b) { + dbg(0, "fatal: no decoder backend available"); + free(d); + return NULL; + } + d->d = d->b->open(fn, nfo); + if (!d->d) { + free(d); + return NULL; + } + return (void*)d; +} + +int ad_info(void *sf, struct adinfo *nfo) { + adecoder *d = (adecoder*) sf; + if (!d) return -1; + return d->b->info(d->d, nfo); +} + +int ad_close(void *sf) { + adecoder *d = (adecoder*) sf; + if (!d) return -1; + int rv = d->b->close(d->d); + free(d); + return rv; +} + +int64_t ad_seek(void *sf, int64_t pos) { + adecoder *d = (adecoder*) sf; + if (!d) return -1; + return d->b->seek(d->d, pos); +} + +ssize_t ad_read(void *sf, float* out, size_t len){ + adecoder *d = (adecoder*) sf; + if (!d) return -1; + return d->b->read(d->d, out, len); +} + +/* + * side-effects: allocates buffer + */ +ssize_t ad_read_mono_dbl(void *sf, struct adinfo *nfo, double* d, size_t len){ + unsigned int c,f; + unsigned int chn = nfo->channels; + if (len<1) return 0; + + static float *buf = NULL; + static size_t bufsiz = 0; + if (!buf || bufsiz != len*chn) { + bufsiz=len*chn; + buf = (float*) realloc((void*)buf, bufsiz * sizeof(float)); + } + + len = ad_read(sf, buf, bufsiz); + + for (f=0;f< (len/chn);f++) { + double val=0.0; + for (c=0;cmeta_data) free(nfo->meta_data); +} + +void ad_dump_nfo(int dbglvl, struct adinfo *nfo) { + dbg(dbglvl, "sample_rate: %u", nfo->sample_rate); + dbg(dbglvl, "channels: %u", nfo->channels); + dbg(dbglvl, "length: %"PRIi64" ms", nfo->length); + dbg(dbglvl, "frames: %"PRIi64, nfo->frames); + dbg(dbglvl, "bit_rate: %d", nfo->bit_rate); + dbg(dbglvl, "bit_depth: %d", nfo->bit_depth); + dbg(dbglvl, "channels: %u", nfo->channels); + dbg(dbglvl, "meta-data: %s", nfo->meta_data?nfo->meta_data:"-"); +} + +void ad_debug_printf(const char* func, int level, const char* format, ...) { + va_list args; + + va_start(args, format); + if (level <= ad_debug_level) { + fprintf(stderr, "%s(): ", func); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + } + va_end(args); +} + +void ad_set_debuglevel(int lvl) { + ad_debug_level = lvl; + if (ad_debug_level<-1) ad_debug_level=-1; + if (ad_debug_level>3) ad_debug_level=3; +} diff --git a/source/modules/audio_decoder/ad_plugin.h b/source/modules/audio_decoder/ad_plugin.h new file mode 100644 index 000000000..17ddec8be --- /dev/null +++ b/source/modules/audio_decoder/ad_plugin.h @@ -0,0 +1,64 @@ +/** + Copyright (C) 2011-2013 Robin Gareus + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser Public License as published by + the Free Software Foundation; either version 2.1, or (at your option) + any later version. + + This program 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 Lesser Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#ifndef __AD_PLUGIN_H__ +#define __AD_PLUGIN_H__ +#include +#include "ad.h" + +#define dbg(A, B, ...) ad_debug_printf(__func__, A, B, ##__VA_ARGS__) + +#ifndef __PRI64_PREFIX +#if (defined __X86_64__ || defined __LP64__) +# define __PRI64_PREFIX "l" +#else +# define __PRI64_PREFIX "ll" +#endif +#endif + +#ifndef PRIu64 +# define PRIu64 __PRI64_PREFIX "u" +#endif +#ifndef PRIi64 +# define PRIi64 __PRI64_PREFIX "i" +#endif + +extern int ad_debug_level; + +void ad_debug_printf(const char* func, int level, const char* format, ...); + +typedef struct { + int (*eval)(const char *); + void * (*open)(const char *, struct adinfo *); + int (*close)(void *); + int (*info)(void *, struct adinfo *); + int64_t (*seek)(void *, int64_t); + ssize_t (*read)(void *, float *, size_t); +} ad_plugin; + +int ad_eval_null(const char *); +void * ad_open_null(const char *, struct adinfo *); +int ad_close_null(void *); +int ad_info_null(void *, struct adinfo *); +int64_t ad_seek_null(void *, int64_t); +ssize_t ad_read_null(void *, float*, size_t); + +/* hardcoded backends */ +const ad_plugin * adp_get_sndfile(); +const ad_plugin * adp_get_ffmpeg(); +#endif diff --git a/source/modules/audio_decoder/ad_soundfile.c b/source/modules/audio_decoder/ad_soundfile.c new file mode 100644 index 000000000..a73ee8df9 --- /dev/null +++ b/source/modules/audio_decoder/ad_soundfile.c @@ -0,0 +1,158 @@ +/** + Copyright (C) 2011-2013 Robin Gareus + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser Public License as published by + the Free Software Foundation; either version 2.1, or (at your option) + any later version. + + This program 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 Lesser Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#include +#include +#include +#include +#include +#include + +#include "ad_plugin.h" + +#ifdef HAVE_SNDFILE + +#include + +/* internal abstraction */ + +typedef struct { + SF_INFO sfinfo; + SNDFILE *sffile; +} sndfile_audio_decoder; + +static int parse_bit_depth(int format) { + /* see http://www.mega-nerd.com/libsndfile/api.html */ + switch (format&0x0f) { + case SF_FORMAT_PCM_S8: return 8; + case SF_FORMAT_PCM_16: return 16; /* Signed 16 bit data */ + case SF_FORMAT_PCM_24: return 24; /* Signed 24 bit data */ + case SF_FORMAT_PCM_32: return 32; /* Signed 32 bit data */ + case SF_FORMAT_PCM_U8: return 8; /* Unsigned 8 bit data (WAV and RAW only) */ + case SF_FORMAT_FLOAT : return 32; /* 32 bit float data */ + case SF_FORMAT_DOUBLE: return 64; /* 64 bit float data */ + default: break; + } + return 0; +} + +static int ad_info_sndfile(void *sf, struct adinfo *nfo) { + sndfile_audio_decoder *priv = (sndfile_audio_decoder*) sf; + if (!priv) return -1; + if (nfo) { + nfo->channels = priv->sfinfo.channels; + nfo->frames = priv->sfinfo.frames; + nfo->sample_rate = priv->sfinfo.samplerate; + nfo->length = priv->sfinfo.samplerate ? (priv->sfinfo.frames * 1000) / priv->sfinfo.samplerate : 0; + nfo->bit_depth = parse_bit_depth(priv->sfinfo.format); + nfo->bit_rate = nfo->bit_depth * nfo->channels * nfo->sample_rate; + nfo->meta_data = NULL; + } + return 0; +} + +static void *ad_open_sndfile(const char *fn, struct adinfo *nfo) { + sndfile_audio_decoder *priv = (sndfile_audio_decoder*) calloc(1, sizeof(sndfile_audio_decoder)); + priv->sfinfo.format=0; + if(!(priv->sffile = sf_open(fn, SFM_READ, &priv->sfinfo))){ + dbg(0, "unable to open file '%s'.", fn); + puts(sf_strerror(NULL)); + int e = sf_error(NULL); + dbg(0, "error=%i", e); + free(priv); + return NULL; + } + ad_info_sndfile(priv, nfo); + return (void*) priv; +} + +static int ad_close_sndfile(void *sf) { + sndfile_audio_decoder *priv = (sndfile_audio_decoder*) sf; + if (!priv) return -1; + if(!sf || sf_close(priv->sffile)) { + dbg(0, "fatal: bad file close.\n"); + return -1; + } + free(priv); + return 0; +} + +static int64_t ad_seek_sndfile(void *sf, int64_t pos) { + sndfile_audio_decoder *priv = (sndfile_audio_decoder*) sf; + if (!priv) return -1; + return sf_seek(priv->sffile, pos, SEEK_SET); +} + +static ssize_t ad_read_sndfile(void *sf, float* d, size_t len) { + sndfile_audio_decoder *priv = (sndfile_audio_decoder*) sf; + if (!priv) return -1; + return sf_read_float (priv->sffile, d, len); +} + +static int ad_eval_sndfile(const char *f) { + char *ext = strrchr(f, '.'); + if (strstr (f, "://")) return 0; + if (!ext) return 5; + /* see http://www.mega-nerd.com/libsndfile/ */ + if (!strcasecmp(ext, ".wav")) return 100; + if (!strcasecmp(ext, ".aiff")) return 100; + if (!strcasecmp(ext, ".aifc")) return 100; + if (!strcasecmp(ext, ".snd")) return 100; + if (!strcasecmp(ext, ".au")) return 100; + if (!strcasecmp(ext, ".paf")) return 100; + if (!strcasecmp(ext, ".iff")) return 100; + if (!strcasecmp(ext, ".svx")) return 100; + if (!strcasecmp(ext, ".sf")) return 100; + if (!strcasecmp(ext, ".vcc")) return 100; + if (!strcasecmp(ext, ".w64")) return 100; + if (!strcasecmp(ext, ".mat4")) return 100; + if (!strcasecmp(ext, ".mat5")) return 100; + if (!strcasecmp(ext, ".pvf5")) return 100; + if (!strcasecmp(ext, ".xi")) return 100; + if (!strcasecmp(ext, ".htk")) return 100; + if (!strcasecmp(ext, ".pvf")) return 100; + if (!strcasecmp(ext, ".sd2")) return 100; +// libsndfile >= 1.0.18 + if (!strcasecmp(ext, ".flac")) return 80; + if (!strcasecmp(ext, ".ogg")) return 80; + return 0; +} +#endif + +static const ad_plugin ad_sndfile = { +#ifdef HAVE_SNDFILE + &ad_eval_sndfile, + &ad_open_sndfile, + &ad_close_sndfile, + &ad_info_sndfile, + &ad_seek_sndfile, + &ad_read_sndfile +#else + &ad_eval_null, + &ad_open_null, + &ad_close_null, + &ad_info_null, + &ad_seek_null, + &ad_read_null +#endif +}; + +/* dlopen handler */ +const ad_plugin * adp_get_sndfile() { + return &ad_sndfile; +} diff --git a/source/modules/audio_decoder/ffcompat.h b/source/modules/audio_decoder/ffcompat.h new file mode 100644 index 000000000..3614af069 --- /dev/null +++ b/source/modules/audio_decoder/ffcompat.h @@ -0,0 +1,95 @@ +/* ffmpeg compatibility wrappers + * + * Copyright 2012,2013 Robin Gareus + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef FFCOMPAT_H +#define FFCOMPAT_H + +#include +#include +#include + +#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE +#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 +#endif + +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(50, 0, 0) +#define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO +#endif + +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 2, 0) +static inline int avformat_open_input(AVFormatContext **ps, const char *filename, void *fmt, void **options) +{ + return av_open_input_file(ps, filename, NULL, 0, NULL); +} +#endif /* avformat < 53.2.0 */ + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 5, 0) +static inline AVCodecContext * +avcodec_alloc_context3(AVCodec *codec __attribute__((unused))) +{ + return avcodec_alloc_context(); +} + +static inline AVStream * +avformat_new_stream(AVFormatContext *s, AVCodec *c) { + return av_new_stream(s,0); +} + +static inline int +avcodec_get_context_defaults3(AVCodecContext *s, AVCodec *codec) +{ + avcodec_get_context_defaults(s); + return 0; +} + +#endif /* < 53.5.0 */ + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 5, 6) +static inline int +avcodec_open2(AVCodecContext *avctx, AVCodec *codec, void **options __attribute__((unused))) +{ + return avcodec_open(avctx, codec); +} +#endif /* <= 53.5.6 */ + +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 5, 0) +static inline int +avformat_find_stream_info(AVFormatContext *ic, void **options) +{ + return av_find_stream_info(ic); +} + +static inline void +avformat_close_input(AVFormatContext **s) +{ + av_close_input_file(*s); +} + +#endif /* < 53.5.0 */ + +#endif /* FFCOMPAT_H */ diff --git a/source/native-plugins/audio-base.hpp b/source/native-plugins/audio-base.hpp new file mode 100644 index 000000000..1d4c5389f --- /dev/null +++ b/source/native-plugins/audio-base.hpp @@ -0,0 +1,387 @@ +/* + * Carla Native Plugins + * Copyright (C) 2013-2017 Filipe Coelho + * + * This program 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 2 of + * the License, or any later version. + * + * This program 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. + * + * For a full copy of the GNU General Public License see the GPL.txt file + */ + +#ifndef AUDIO_BASE_HPP_INCLUDED +#define AUDIO_BASE_HPP_INCLUDED + +#include "CarlaThread.hpp" +#include "CarlaMathUtils.hpp" + +extern "C" { +#include "audio_decoder/ad.h" +} + +typedef struct adinfo ADInfo; + +struct AudioFilePool { + float* buffer[2]; + uint32_t startFrame; + uint32_t size; + +#ifdef CARLA_PROPER_CPP11_SUPPORT + AudioFilePool() + : buffer{nullptr}, + startFrame(0), + size(0) {} +#else + AudioFilePool() + : startFrame(0), + size(0) + { + buffer[0] = buffer[1] = nullptr; + } +#endif + + ~AudioFilePool() + { + CARLA_ASSERT(buffer[0] == nullptr); + CARLA_ASSERT(buffer[1] == nullptr); + CARLA_ASSERT(startFrame == 0); + CARLA_ASSERT(size == 0); + } + + void create(const uint32_t sampleRate) + { + CARLA_ASSERT(buffer[0] == nullptr); + CARLA_ASSERT(buffer[1] == nullptr); + CARLA_ASSERT(startFrame == 0); + CARLA_ASSERT(size == 0); + + size = sampleRate * 2; + + buffer[0] = new float[size]; + buffer[1] = new float[size]; + + reset(); + } + + void destroy() + { + CARLA_ASSERT(buffer[0] != nullptr); + CARLA_ASSERT(buffer[1] != nullptr); + CARLA_ASSERT(size != 0); + + if (buffer[0] != nullptr) + { + delete[] buffer[0]; + buffer[0] = nullptr; + } + + if (buffer[1] != nullptr) + { + delete[] buffer[1]; + buffer[1] = nullptr; + } + + startFrame = 0; + size = 0; + } + + void reset() + { + startFrame = 0; + CARLA_SAFE_ASSERT_RETURN(size != 0,); + + carla_zeroFloats(buffer[0], size); + carla_zeroFloats(buffer[1], size); + } +}; + +class AbstractAudioPlayer +{ +public: + virtual ~AbstractAudioPlayer() {} + virtual uint32_t getLastFrame() const = 0; +}; + +class AudioFileThread : public CarlaThread +{ +public: + AudioFileThread(AbstractAudioPlayer* const player, const double sampleRate) + : CarlaThread("AudioFileThread"), + kPlayer(player), + fNeedsRead(false), + fQuitNow(true), + fFilePtr(nullptr) + { + CARLA_ASSERT(kPlayer != nullptr); + + static bool adInitiated = false; + + if (! adInitiated) + { + ad_init(); + adInitiated = true; + } + + ad_clear_nfo(&fFileNfo); + + fPool.create(sampleRate); + } + + ~AudioFileThread() override + { + CARLA_ASSERT(fQuitNow); + CARLA_ASSERT(! isThreadRunning()); + + if (fFilePtr != nullptr) + ad_close(fFilePtr); + + fPool.destroy(); + } + + void startNow() + { + fNeedsRead = true; + fQuitNow = false; + startThread(); + } + + void stopNow() + { + fNeedsRead = false; + fQuitNow = true; + + stopThread(1000); + + const CarlaMutexLocker cml(fMutex); + fPool.reset(); + } + + uint32_t getMaxFrame() const + { + return fFileNfo.frames > 0 ? fFileNfo.frames : 0; + } + + void setNeedsRead() + { + fNeedsRead = true; + } + + bool loadFilename(const char* const filename) + { + CARLA_ASSERT(! isThreadRunning()); + CARLA_ASSERT(filename != nullptr); + + fPool.startFrame = 0; + + // clear old data + if (fFilePtr != nullptr) + { + ad_close(fFilePtr); + fFilePtr = nullptr; + } + + ad_clear_nfo(&fFileNfo); + + // open new + fFilePtr = ad_open(filename, &fFileNfo); + + if (fFilePtr == nullptr) + return false; + + ad_dump_nfo(99, &fFileNfo); + + if (fFileNfo.frames == 0) + carla_stderr("L: filename \"%s\" has 0 frames", filename); + + if ((fFileNfo.channels == 1 || fFileNfo.channels == 2) && fFileNfo.frames > 0) + { + // valid + readPoll(); + return true; + } + else + { + // invalid + ad_clear_nfo(&fFileNfo); + ad_close(fFilePtr); + fFilePtr = nullptr; + return false; + } + } + + void tryPutData(AudioFilePool& pool) + { + CARLA_ASSERT(pool.size == fPool.size); + + if (pool.size != fPool.size) + return; + if (! fMutex.tryLock()) + return; + + //if (pool.startFrame != fPool.startFrame || pool.buffer[0] != fPool.buffer[0] || pool.buffer[1] != fPool.buffer[1]) + { + pool.startFrame = fPool.startFrame; + carla_copyFloats(pool.buffer[0], fPool.buffer[0], fPool.size); + carla_copyFloats(pool.buffer[1], fPool.buffer[1], fPool.size); + } + + fMutex.unlock(); + } + + void readPoll() + { + if (fFileNfo.frames <= 0 || fFilePtr == nullptr) + { + carla_stderr("R: no song loaded"); + fNeedsRead = false; + return; + } + + int64_t lastFrame = kPlayer->getLastFrame(); + int64_t readFrame = lastFrame; + int64_t maxFrame = fFileNfo.frames; + + if (lastFrame >= maxFrame) + { +#if 0 + if (false) + //if (handlePtr->loopMode) + { + carla_stderr("R: DEBUG read loop, lastFrame:%i, maxFrame:%i", lastFrame, maxFrame); + + if (maxFrame >= static_cast(fPool.size)) + { + readFrame %= maxFrame; + } + else + { + readFrame = 0; + lastFrame -= lastFrame % maxFrame; + } + } + else +#endif + { + carla_stderr("R: transport out of bounds"); + fNeedsRead = false; + return; + } + } + + // temp data buffer + const size_t tmpSize = fPool.size * fFileNfo.channels; + + float tmpData[tmpSize]; + carla_zeroFloats(tmpData, tmpSize); + + { + carla_stderr("R: poll data - reading at %li:%02li", readFrame/44100/60, (readFrame/44100) % 60); + + ad_seek(fFilePtr, readFrame); + ssize_t i, j, rv = ad_read(fFilePtr, tmpData, tmpSize); + i = j = 0; + + // lock, and put data asap + const CarlaMutexLocker cml(fMutex); + + for (; i < fPool.size && j < rv; ++j) + { + if (fFileNfo.channels == 1) + { + fPool.buffer[0][i] = tmpData[j]; + fPool.buffer[1][i] = tmpData[j]; + i++; + } + else + { + if (j % 2 == 0) + { + fPool.buffer[0][i] = tmpData[j]; + } + else + { + fPool.buffer[1][i] = tmpData[j]; + i++; + } + } + } + +#if 0 + if (false) + //if (handlePtr->loopMode && i < fPool.size) + { + while (i < fPool.size) + { + for (j=0; i < fPool.size && j < rv; ++j) + { + if (fFileNfo.channels == 1) + { + fPool.buffer[0][i] = tmpData[j]; + fPool.buffer[1][i] = tmpData[j]; + i++; + } + else + { + if (j % 2 == 0) + { + fPool.buffer[0][i] = tmpData[j]; + } + else + { + fPool.buffer[1][i] = tmpData[j]; + i++; + } + } + } + } + } + else +#endif + { + for (; i < fPool.size; ++i) + { + fPool.buffer[0][i] = 0.0f; + fPool.buffer[1][i] = 0.0f; + } + } + + fPool.startFrame = lastFrame; + } + + fNeedsRead = false; + } + +protected: + void run() override + { + while (! fQuitNow) + { + const uint32_t lastFrame(kPlayer->getLastFrame()); + + if (fNeedsRead || lastFrame < fPool.startFrame || (lastFrame - fPool.startFrame >= fPool.size*3/4 && lastFrame < fFileNfo.frames)) + readPoll(); + else + carla_msleep(50); + } + } + +private: + AbstractAudioPlayer* const kPlayer; + + bool fNeedsRead; + bool fQuitNow; + + void* fFilePtr; + ADInfo fFileNfo; + + AudioFilePool fPool; + CarlaMutex fMutex; +}; + +#endif // AUDIO_BASE_HPP_INCLUDED diff --git a/source/native-plugins/audio-file.cpp b/source/native-plugins/audio-file.cpp index 87633b4cd..14dbc1574 100644 --- a/source/native-plugins/audio-file.cpp +++ b/source/native-plugins/audio-file.cpp @@ -1,6 +1,6 @@ /* * Carla Native Plugins - * Copyright (C) 2012-2017 Filipe Coelho + * Copyright (C) 2013 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -12,42 +12,41 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * For a full copy of the GNU General Public License see the doc/GPL.txt file. + * For a full copy of the GNU General Public License see the GPL.txt file */ #include "CarlaNative.hpp" - -#if 0 - -#include "CarlaMutex.hpp" #include "CarlaString.hpp" -using namespace water; +#include "audio-base.hpp" -// ----------------------------------------------------------------------- +#define PROGRAM_COUNT 16 -class AudioFilePlugin : public NativePluginClass +class AudioFilePlugin : public NativePluginClass, + public AbstractAudioPlayer { public: AudioFilePlugin(const NativeHostDescriptor* const host) : NativePluginClass(host), + AbstractAudioPlayer(), fLoopMode(false), fDoProcess(false), - fLength(0), - //fThread("AudioFilePluginThread"), - fReaderBuffer(), - fReaderMutex(), - fReader(), - fReaderSource() + fLastFrame(0), + fMaxFrame(0), + fThread(this, getSampleRate()) { - fReaderBuffer.setSize(2, static_cast(getBufferSize())); + fPool.create(getSampleRate()); } ~AudioFilePlugin() override { - //fThread.stopThread(-1); - fReader = nullptr; - fReaderSource = nullptr; + fPool.destroy(); + fThread.stopNow(); + } + + uint32_t getLastFrame() const override + { + return fLastFrame; } protected: @@ -56,7 +55,7 @@ protected: uint32_t getParameterCount() const override { - return 1; + return 0; // TODO - loopMode } const NativeParameter* getParameterInfo(const uint32_t index) const override @@ -97,77 +96,94 @@ protected: if (index != 0) return; - const bool loopMode(value > 0.5f); + bool b = (value > 0.5f); - if (fLoopMode == loopMode) + if (b == fLoopMode) return; - fLoopMode = loopMode; - - const CarlaMutexLocker cml(fReaderMutex); - - if (fReaderSource != nullptr) - fReaderSource->setLooping(loopMode); + fLoopMode = b; + fThread.setNeedsRead(); } void setCustomData(const char* const key, const char* const value) override { - CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); - CARLA_SAFE_ASSERT_RETURN(value != nullptr && value[0] != '\0',); - if (std::strcmp(key, "file") != 0) return; - _loadAudioFile(value); + loadFilename(value); } // ------------------------------------------------------------------- // Plugin process calls - void process(float**, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override + void process(float**, float** const outBuffer, const uint32_t frames, const NativeMidiEvent*, uint32_t) override { const NativeTimeInfo* const timePos(getTimeInfo()); - float* const out1(outBuffer[0]); - float* const out2(outBuffer[1]); + float* out1 = outBuffer[0]; + float* out2 = outBuffer[1]; - if (fLength == 0 || ! fDoProcess) + if (! fDoProcess) { //carla_stderr("P: no process"); - carla_zeroFloats(out1, iframes); - carla_zeroFloats(out2, iframes); + fLastFrame = timePos->frame; + carla_zeroFloats(out1, frames); + carla_zeroFloats(out2, frames); return; } - const int64_t nextReadPos(fLoopMode ? (static_cast(timePos->frame) % fLength) : static_cast(timePos->frame)); - // not playing if (! timePos->playing) { //carla_stderr("P: not playing"); - carla_zeroFloats(out1, iframes); - carla_zeroFloats(out2, iframes); + fLastFrame = timePos->frame; - const CarlaMutexLocker cml(fReaderMutex); - - if (fReaderSource != nullptr) - fReaderSource->setNextReadPosition(nextReadPos); + if (timePos->frame == 0 && fLastFrame > 0) + fThread.setNeedsRead(); + carla_zeroFloats(out1, frames); + carla_zeroFloats(out2, frames); return; } - const CarlaMutexLocker cml(fReaderMutex); + fThread.tryPutData(fPool); + + // out of reach + if (timePos->frame + frames < fPool.startFrame || timePos->frame >= fMaxFrame) /*&& ! loopMode)*/ + { + //carla_stderr("P: out of reach"); + fLastFrame = timePos->frame; - if (fReaderSource != nullptr) - fReaderSource->setNextReadPosition(nextReadPos); + if (timePos->frame + frames < fPool.startFrame) + fThread.setNeedsRead(); - if (fReader == nullptr) + carla_zeroFloats(out1, frames); + carla_zeroFloats(out2, frames); return; + } - fReader->read(&fReaderBuffer, 0, iframes, nextReadPos, true, true); + int64_t poolFrame = (int64_t)timePos->frame - fPool.startFrame; + int64_t poolSize = fPool.size; - carla_copyFloats(out1, fReaderBuffer.getReadPointer(0), frames); - carla_copyFloats(out2, fReaderBuffer.getReadPointer(1), frames); + for (uint32_t i=0; i < frames; ++i, ++poolFrame) + { + if (poolFrame >= 0 && poolFrame < poolSize) + { + out1[i] = fPool.buffer[0][poolFrame]; + out2[i] = fPool.buffer[1][poolFrame]; + + // reset + fPool.buffer[0][poolFrame] = 0.0f; + fPool.buffer[1][poolFrame] = 0.0f; + } + else + { + out1[i] = 0.0f; + out2[i] = 0.0f; + } + } + + fLastFrame = timePos->frame; } // ------------------------------------------------------------------- @@ -184,85 +200,41 @@ protected: uiClosed(); } - // ------------------------------------------------------------------- - // Plugin dispatcher calls - - void bufferSizeChanged(const uint32_t bufferSize) override - { - fReaderBuffer.setSize(2, static_cast(bufferSize)); - } - private: bool fLoopMode; bool fDoProcess; - int64_t fLength; - //TimeSliceThread fThread; + uint32_t fLastFrame; + uint32_t fMaxFrame; - AudioSampleBuffer fReaderBuffer; - CarlaMutex fReaderMutex; + AudioFilePool fPool; + AudioFileThread fThread; - ScopedPointer fReader; - ScopedPointer fReaderSource; - - void _loadAudioFile(const char* const filename) + void loadFilename(const char* const filename) { - carla_stdout("AudioFilePlugin::loadFilename(\"%s\")", filename); - - fDoProcess = false; - fLength = 0; + CARLA_ASSERT(filename != nullptr); + carla_debug("AudioFilePlugin::loadFilename(\"%s\")", filename); - //fThread.stopThread(-1); + fThread.stopNow(); + if (filename == nullptr || *filename == '\0') { - fReaderMutex.lock(); - AudioFormatReader* const reader(fReader.release()); - AudioFormatReaderSource* const readerSource(fReaderSource.release()); - fReaderMutex.unlock(); - - delete readerSource; - delete reader; - } - - const String jfilename = String(CharPointer_UTF8(filename)); - File file(jfilename); - - if (! file.existsAsFile()) + fDoProcess = false; + fMaxFrame = 0; return; + } - AudioFormatManager& afm(getAudioFormatManagerInstance()); - - AudioFormat* const format(afm.findFormatForFileExtension(file.getFileExtension())); - CARLA_SAFE_ASSERT_RETURN(format != nullptr,); - - if (MemoryMappedAudioFormatReader* const memReader = format->createMemoryMappedReader(file)) + if (fThread.loadFilename(filename)) { - memReader->mapEntireFile(); - fReader = memReader; - - carla_stdout("Using memory mapped read file"); + fThread.startNow(); + fMaxFrame = fThread.getMaxFrame(); + fDoProcess = true; } else { - AudioFormatReader* const reader(afm.createReaderFor(file)); - CARLA_SAFE_ASSERT_RETURN(reader != nullptr,); - - // this code can be used for very large files - //fThread.startThread(); - //BufferingAudioReader* const bufferingReader(new BufferingAudioReader(reader, fThread, getSampleRate()*2)); - //bufferingReader->setReadTimeout(50); - - AudioFormatReaderSource* const readerSource(new AudioFormatReaderSource(/*bufferingReader*/reader, false)); - readerSource->setLooping(fLoopMode); - - fReaderSource = readerSource; - fReader = reader; - - carla_stdout("Using regular read file"); + fDoProcess = false; + fMaxFrame = 0; } - - fLength = fReader->lengthInSamples; - fDoProcess = true; } PluginClassEND(AudioFilePlugin) @@ -273,14 +245,16 @@ private: static const NativePluginDescriptor audiofileDesc = { /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY, - /* hints */ static_cast(NATIVE_PLUGIN_HAS_UI - |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE), + /* hints */ static_cast(NATIVE_PLUGIN_IS_RTSAFE + |NATIVE_PLUGIN_HAS_UI + |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE + |NATIVE_PLUGIN_USES_TIME), /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING, /* audioIns */ 0, /* audioOuts */ 2, /* midiIns */ 0, /* midiOuts */ 0, - /* paramIns */ 1, + /* paramIns */ 0, // TODO - loopMode /* paramOuts */ 0, /* name */ "Audio File", /* label */ "audiofile", @@ -291,15 +265,13 @@ static const NativePluginDescriptor audiofileDesc = { // ----------------------------------------------------------------------- -#endif - CARLA_EXPORT void carla_register_native_plugin_audiofile(); CARLA_EXPORT void carla_register_native_plugin_audiofile() { - //carla_register_native_plugin(&audiofileDesc); + carla_register_native_plugin(&audiofileDesc); } // ----------------------------------------------------------------------- diff --git a/source/plugin/Makefile b/source/plugin/Makefile index 4aa933a64..2a3eecee4 100644 --- a/source/plugin/Makefile +++ b/source/plugin/Makefile @@ -50,6 +50,8 @@ endif LIBS = $(MODULEDIR)/carla_engine_plugin.a LIBS += $(MODULEDIR)/carla_plugin.a LIBS += $(MODULEDIR)/jackbridge.a + +LIBS += $(MODULEDIR)/audio_decoder.a LIBS += $(MODULEDIR)/lilv.a LIBS += $(MODULEDIR)/native-plugins.a LIBS += $(MODULEDIR)/rtmempool.a @@ -72,25 +74,13 @@ LINK_FLAGS += $(NATIVE_PLUGINS_LIBS) LINK_FLAGS += $(RTMEMPOOL_LIBS) LINK_FLAGS += $(WATER_LIBS) -ifeq ($(HAVE_LIBLO),true) +LINK_FLAGS += $(FFMPEG_LIBS) LINK_FLAGS += $(LIBLO_LIBS) -endif - -ifeq ($(HAVE_LIBMAGIC),true) LINK_FLAGS += $(MAGIC_LIBS) -endif - -ifeq ($(HAVE_FLUIDSYNTH),true) LINK_FLAGS += $(FLUIDSYNTH_LIBS) -endif - -ifeq ($(HAVE_LINUXSAMPLER),true) LINK_FLAGS += $(LINUXSAMPLER_LIBS) -endif - -ifeq ($(HAVE_X11),true) +LINK_FLAGS += $(SNDFILE_LIBS) LINK_FLAGS += $(X11_LIBS) -endif # ----------------------------------------------------------------------------------------------------------------------------