summaryrefslogtreecommitdiff
path: root/plugins/ffmpeg
diff options
context:
space:
mode:
authorGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-12-23 22:43:31 +0100
committerGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-12-23 22:43:31 +0100
commit5f008bc7ad0770ca920a9340839510c46c3dcd3b (patch)
treed1cf3bfc6e344a87a2f1c0504b75736d899a908c /plugins/ffmpeg
parentd55be151ce3a2244b0143ab2fff986df5bfb227a (diff)
ffmpeg plugin initial commit
Diffstat (limited to 'plugins/ffmpeg')
-rw-r--r--plugins/ffmpeg/Makefile.am9
-rw-r--r--plugins/ffmpeg/ffmpeg.c491
2 files changed, 500 insertions, 0 deletions
diff --git a/plugins/ffmpeg/Makefile.am b/plugins/ffmpeg/Makefile.am
new file mode 100644
index 00000000..0793990f
--- /dev/null
+++ b/plugins/ffmpeg/Makefile.am
@@ -0,0 +1,9 @@
+if HAVE_FFMPEG
+ffmpegdir = $(libdir)/$(PACKAGE)
+pkglib_LTLIBRARIES = ffmpeg.la
+ffmpeg_la_SOURCES = ffmpeg.c
+ffmpeg_la_LDFLAGS = -module
+
+ffmpeg_la_LIBADD = $(LDADD) $(FFMPEG_DEPS_LIBS)
+AM_CFLAGS = $(CFLAGS) -std=c99 ${FFMPEG_DEPS_CFLAGS}
+endif
diff --git a/plugins/ffmpeg/ffmpeg.c b/plugins/ffmpeg/ffmpeg.c
new file mode 100644
index 00000000..2f8fb63e
--- /dev/null
+++ b/plugins/ffmpeg/ffmpeg.c
@@ -0,0 +1,491 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ 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 (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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+#include "../../deadbeef.h"
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+#include <libavutil/avstring.h>
+
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+static DB_decoder_t plugin;
+static DB_functions_t *deadbeef;
+
+static const char * exts[] = { "m4a", NULL }; // e.g. mp3
+static const char *filetypes[] = { "m4a", NULL }; // e.g. MP3
+
+#define FF_PROTOCOL_NAME "deadbeef"
+
+static int currentsample;
+
+static AVCodec *codec;
+static AVCodecContext *ctx;
+static AVFormatContext *fctx;
+static AVPacket pkt;
+static int stream_id;
+
+static int left_in_packet;
+static int have_packet;
+
+static char buffer[AVCODEC_MAX_AUDIO_FRAME_SIZE];
+static int left_in_buffer;
+
+static int
+ffmpeg_init (DB_playItem_t *it) {
+ // prepare to decode the track
+ // return -1 on failure
+
+ int ret;
+ int l = strlen (it->fname);
+ char *uri = alloca (l + sizeof (FF_PROTOCOL_NAME) + 1);
+ int i;
+
+ // construct uri
+ memcpy (uri, FF_PROTOCOL_NAME, sizeof (FF_PROTOCOL_NAME)-1);
+ memcpy (uri + sizeof (FF_PROTOCOL_NAME)-1, ":", 1);
+ memcpy (uri + sizeof (FF_PROTOCOL_NAME), it->fname, l);
+ uri[sizeof (FF_PROTOCOL_NAME) + l] = 0;
+ trace ("ffmpeg: uri: %s\n", uri);
+
+ // open file
+ if ((ret = av_open_input_file(&fctx, uri, NULL, 0, NULL)) < 0) {
+ trace ("fctx is %p, ret %d/%s", fctx, ret, strerror(-ret));
+ return -1;
+ }
+
+ for (i = 0; i < fctx->nb_streams; i++)
+ {
+ ctx = fctx->streams[i]->codec;
+ if (ctx->codec_type == CODEC_TYPE_AUDIO)
+ {
+ av_find_stream_info(fctx);
+ codec = avcodec_find_decoder(ctx->codec_id);
+ if (codec != NULL)
+ break;
+ }
+ }
+
+ if (codec == NULL)
+ {
+ trace ("ffmpeg can't decode %s\n", it->fname);
+ av_close_input_file(fctx);
+ return -1;
+ }
+ stream_id = i;
+ trace ("ffmpeg can decode %s\n", it->fname);
+ trace ("ffmpeg: codec=%s, stream=%d\n", codec->name, i);
+
+ if (avcodec_open (ctx, codec) < 0) {
+ trace ("ffmpeg: avcodec_open failed\n");
+ av_close_input_file(fctx);
+ return -1;
+ }
+
+ int bps = av_get_bits_per_sample_format (ctx->sample_fmt);
+ int samplerate = ctx->sample_rate;
+ float duration = (float)fctx->duration / AV_TIME_BASE;
+ trace ("ffmpeg: bits per sample is %d\n", bps);
+ trace ("ffmpeg: samplerate is %d\n", samplerate);
+ trace ("ffmpeg: duration is %f\n", duration);
+
+ int totalsamples = fctx->duration * samplerate / AV_TIME_BASE;
+ left_in_packet = 0;
+ left_in_buffer = 0;
+
+ memset (&pkt, 0, sizeof (pkt));
+ have_packet = 0;
+
+ // fill in mandatory plugin fields
+ plugin.info.readpos = 0;
+ plugin.info.bps = bps;
+ plugin.info.channels = ctx->channels;
+ plugin.info.samplerate = samplerate;
+ return 0;
+}
+
+static void
+ffmpeg_free (void) {
+ // free everything allocated in _init and _read_int16
+ if (have_packet) {
+ av_free_packet (&pkt);
+ have_packet = 0;
+ }
+
+ if (fctx) {
+ av_close_input_file(fctx);
+ fctx = NULL;
+ }
+ if (ctx) {
+ avcodec_close (ctx);
+ ctx = NULL;
+ }
+
+ codec = NULL;
+}
+
+static int
+ffmpeg_read_int16 (char *bytes, int size) {
+ // try decode `size' bytes
+ // return number of decoded bytes
+ // return 0 on EOF
+
+ int initsize = size;
+
+ while (size > 0) {
+
+ if (left_in_buffer > 0) {
+ int sz = min (size, left_in_buffer);
+ memcpy (bytes, buffer, sz);
+ if (sz != left_in_buffer) {
+ memmove (buffer, buffer+sz, left_in_buffer-sz);
+ }
+ left_in_buffer -= sz;
+ size -= sz;
+ bytes += sz;
+ }
+
+ while (left_in_packet > 0 && size > 0) {
+ int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+ int len;
+ //trace ("in: out_size=%d(%d), size=%d\n", out_size, AVCODEC_MAX_AUDIO_FRAME_SIZE, size);
+#if (LIBAVCODEC_VERSION_MAJOR <= 52) && (LIBAVCODEC_VERSION_MINOR <= 25)
+ len = avcodec_decode_audio2(ctx, (int16_t *)buffer, &out_size, pkt.data, pkt.size);
+#else
+ len = avcodec_decode_audio3(ctx, (int16_t *)buffer, &out_size, &pkt);
+#endif
+ //trace ("out: out_size=%d, len=%d\n", out_size, len);
+ if (len <= 0) {
+ break;
+ }
+ left_in_packet -= len;
+ left_in_buffer = out_size;
+ }
+ if (size == 0) {
+ break;
+ }
+
+ // read next packet
+ if (have_packet) {
+ av_free_packet (&pkt);
+ have_packet = 0;
+ }
+ int errcount = 0;
+ for (;;) {
+ int ret;
+ if ((ret = av_read_frame(fctx, &pkt)) < 0) {
+ trace ("ffmpeg: error %d\n", ret);
+ if (ret == AVERROR_EOF || ret == -1) {
+ ret = -1;
+ break;
+ }
+ else {
+ if (++errcount > 4) {
+ trace ("ffmpeg: too many errors in a row (last is %d); interrupting stream\n", ret);
+ ret = -1;
+ break;
+ }
+ else {
+ continue;
+ }
+ }
+ }
+ else {
+ errcount = 0;
+ }
+ if (ret == -1) {
+ break;
+ }
+ //trace ("idx:%d, stream:%d\n", pkt.stream_index, stream_id);
+ if (pkt.stream_index != stream_id) {
+ av_free_packet (&pkt);
+ continue;
+ }
+ //trace ("got packet: size=%d\n", pkt.size);
+ have_packet = 1;
+ left_in_packet = pkt.size;
+ break;
+ }
+ if (!have_packet) {
+ break;
+ }
+ }
+
+ plugin.info.readpos = (float)currentsample / plugin.info.samplerate;
+ return initsize-size;
+}
+
+static int
+ffmpeg_seek_sample (int sample) {
+ // seek to specified sample (frame)
+ // return 0 on success
+ // return -1 on failure
+ if (have_packet) {
+ av_free_packet (&pkt);
+ have_packet = 0;
+ }
+ left_in_packet = 0;
+ left_in_buffer = 0;
+ if (av_seek_frame(fctx, -1, (int64_t)sample/ plugin.info.samplerate * AV_TIME_BASE , AVSEEK_FLAG_ANY) < 0) {
+ trace ("ffmpeg: seek error\n");
+ return -1;
+ }
+
+ // update readpos
+ plugin.info.readpos = (float)sample / plugin.info.samplerate;
+ return 0;
+}
+
+static int
+ffmpeg_seek (float time) {
+ // seek to specified time in seconds
+ // return 0 on success
+ // return -1 on failure
+ return ffmpeg_seek_sample (time * plugin.info.samplerate);
+}
+
+static DB_playItem_t *
+ffmpeg_insert (DB_playItem_t *after, const char *fname) {
+ // read information from the track
+ // load/process cuesheet if exists
+ // insert track into playlist
+ // return track pointer on success
+ // return NULL on failure
+
+ AVCodec *codec = NULL;
+ AVCodecContext *ctx = NULL;
+ AVFormatContext *fctx = NULL;
+ int ret;
+ int l = strlen (fname);
+ char *uri = alloca (l + sizeof (FF_PROTOCOL_NAME) + 1);
+ int i;
+
+ // construct uri
+ memcpy (uri, FF_PROTOCOL_NAME, sizeof (FF_PROTOCOL_NAME)-1);
+ memcpy (uri + sizeof (FF_PROTOCOL_NAME)-1, ":", 1);
+ memcpy (uri + sizeof (FF_PROTOCOL_NAME), fname, l);
+ uri[sizeof (FF_PROTOCOL_NAME) + l] = 0;
+ trace ("ffmpeg: uri: %s\n", uri);
+
+ // open file
+ if ((ret = av_open_input_file(&fctx, uri, NULL, 0, NULL)) < 0) {
+ trace ("fctx is %p, ret %d/%s", fctx, ret, strerror(-ret));
+ return NULL;
+ }
+
+ for (i = 0; i < fctx->nb_streams; i++)
+ {
+ ctx = fctx->streams[i]->codec;
+ if (ctx->codec_type == CODEC_TYPE_AUDIO)
+ {
+ av_find_stream_info(fctx);
+ codec = avcodec_find_decoder(ctx->codec_id);
+ if (codec != NULL)
+ break;
+ }
+ }
+
+ if (codec == NULL)
+ {
+ trace ("ffmpeg can't decode %s\n", fname);
+ av_close_input_file(fctx);
+ return NULL;
+ }
+ trace ("ffmpeg can decode %s\n", fname);
+ trace ("ffmpeg: codec=%s, stream=%d\n", codec->name, i);
+
+ if (avcodec_open (ctx, codec) < 0) {
+ trace ("ffmpeg: avcodec_open failed\n");
+ av_close_input_file(fctx);
+ return NULL;
+ }
+
+
+ int bps = av_get_bits_per_sample_format (ctx->sample_fmt);
+ int samplerate = ctx->sample_rate;
+ float duration = (float)fctx->duration / AV_TIME_BASE;
+ trace ("ffmpeg: bits per sample is %d\n", bps);
+ trace ("ffmpeg: samplerate is %d\n", samplerate);
+ trace ("ffmpeg: duration is %f\n", duration);
+
+ int totalsamples = fctx->duration * samplerate / AV_TIME_BASE;
+
+// FIXME: uncomment that when startsample/endsample are correctly handled
+#if 0
+ // embedded cuesheet not found, try external one
+ DB_playItem_t *cue = deadbeef->pl_insert_cue (after, fname, &plugin, plugin.filetypes[0], totalsamples, samplerate);
+ if (cue) {
+ // cuesheet loaded
+ av_close_input_file(fctx);
+ return cue;
+ }
+#endif
+ DB_playItem_t *it = deadbeef->pl_item_alloc ();
+ it->decoder = &plugin;
+ it->fname = strdup (fname);
+ it->filetype = filetypes[0];
+ deadbeef->pl_set_item_duration (it, duration);
+
+ // add metainfo
+#if LIBAVFORMAT_VERSION_INT < (53<<16)
+ if (!strlen (fctx->title)) {
+ // title is empty, this call will set track title to filename without extension
+ deadbeef->pl_add_meta (it, "title", NULL);
+ }
+ else {
+ deadbeef->pl_add_meta (it, "title", fctx->title);
+ }
+ deadbeef->pl_add_meta (it, "artist", fctx->author);
+ deadbeef->pl_add_meta (it, "album", fctx->album);
+ deadbeef->pl_add_meta (it, "year", fctx->album);
+ deadbeef->pl_add_meta (it, "copyright", fctx->copyright);
+ deadbeef->pl_add_meta (it, "comment", fctx->comment);
+ deadbeef->pl_add_meta (it, "genre", fctx->genre);
+
+ char tmp[10];
+ snprintf (tmp, sizeof (tmp), "%d", fctx->year);
+ deadbeef->pl_add_meta (it, "year", tmp);
+ snprintf (tmp, sizeof (tmp), "%d", fctx->track);
+ deadbeef->pl_add_meta (it, "track", tmp);
+#else
+// read using other means?
+// av_metadata_get?
+#endif
+ // free decoder
+ av_close_input_file(fctx);
+
+ // now the track is ready, insert into playlist
+ after = deadbeef->pl_insert_item (after, it);
+ return after;
+}
+
+// vfs wrapper for ffmpeg
+static int
+ffmpeg_vfs_open(URLContext *h, const char *filename, int flags)
+{
+ DB_FILE *f;
+ av_strstart(filename, FF_PROTOCOL_NAME ":", &filename);
+ if (flags & URL_WRONLY) {
+ return -ENOENT;
+ } else {
+ f = deadbeef->fopen (filename);
+ }
+
+ if (f == NULL)
+ return -ENOENT;
+
+ h->priv_data = f;
+ return 0;
+}
+
+static int
+ffmpeg_vfs_read(URLContext *h, unsigned char *buf, int size)
+{
+ int res = deadbeef->fread (buf, 1, size, h->priv_data);
+ return res;
+}
+
+static int
+ffmpeg_vfs_write(URLContext *h, unsigned char *buf, int size)
+{
+ return -1;
+}
+
+static int64_t
+ffmpeg_vfs_seek(URLContext *h, int64_t pos, int whence)
+{
+ DB_FILE *f = h->priv_data;
+
+ if (whence == AVSEEK_SIZE) {
+ return f->vfs->streaming ? -1 : deadbeef->fgetlength (h->priv_data);
+ }
+ int ret = deadbeef->fseek (h->priv_data, pos, whence);
+ return ret;
+}
+
+static int
+ffmpeg_vfs_close(URLContext *h)
+{
+ deadbeef->fclose (h->priv_data);
+ return 0;
+}
+
+static URLProtocol vfswrapper = {
+ .name = FF_PROTOCOL_NAME,
+ .url_open = ffmpeg_vfs_open,
+ .url_read = ffmpeg_vfs_read,
+ .url_write = ffmpeg_vfs_write,
+ .url_seek = ffmpeg_vfs_seek,
+ .url_close = ffmpeg_vfs_close,
+};
+
+static int
+ffmpeg_start (void) {
+ // do one-time plugin initialization here
+ // e.g. starting threads for background processing, subscribing to events, etc
+ // return 0 on success
+ // return -1 on failure
+ avcodec_init ();
+ av_register_all ();
+ av_register_protocol (&vfswrapper);
+ return 0;
+}
+static int
+ffmpeg_stop (void) {
+ // undo everything done in _start here
+ // return 0 on success
+ // return -1 on failure
+ trace ("ffmpeg stop\n");
+ return 0;
+}
+
+// define plugin interface
+static DB_decoder_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_DECODER,
+ .plugin.name = "FFMPEG audio player",
+ .plugin.descr = "decodes audio formats using FFMPEG libavcodec",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .plugin.start = ffmpeg_start,
+ .plugin.stop = ffmpeg_stop,
+ .init = ffmpeg_init,
+ .free = ffmpeg_free,
+ .read_int16 = ffmpeg_read_int16,
+// .read_float32 = ffmpeg_read_float32,
+ .seek = ffmpeg_seek,
+ .seek_sample = ffmpeg_seek_sample,
+ .insert = ffmpeg_insert,
+ .exts = exts,
+ .id = "ffmpeg",
+ .filetypes = filetypes
+};
+
+DB_plugin_t *
+ffmpeg_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}
+