/* DeaDBeeF - ultimate music player for GNU/Linux systems with X11 Copyright (C) 2009-2010 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 #include #include //#include #include #include "../../deadbeef.h" #if !FFMPEG_OLD #include #include #include #include #else #include #include #include #include #define AVERROR_EOF AVERROR(EPIPE) #if LIBAVFORMAT_VERSION_MAJOR < 53 #define av_register_protocol register_protocol #endif #endif #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", "mpc", "mp+", "mpp", "wma", "shn", "aa3", "oma", "ac3", "vqf", "tta", NULL }; enum { FT_M4A = 0, FT_MUSEPACK = 1, FT_WMA = 2, FT_SHORTEN = 3, FT_ATRAC3 = 4, FT_VQF = 5, FT_TTA = 6, FT_UNKNOWN = 6 }; static const char *filetypes[] = { "M4A", "MusePack", "WMA", "Shorten", "atrac3", "VQF", "TTA", "FFMPEG", NULL }; #define FF_PROTOCOL_NAME "deadbeef" typedef struct { DB_fileinfo_t info; AVCodec *codec; AVCodecContext *ctx; AVFormatContext *fctx; AVPacket pkt; int stream_id; int left_in_packet; int have_packet; char *buffer; // must be AVCODEC_MAX_AUDIO_FRAME_SIZE int left_in_buffer; int startsample; int endsample; int currentsample; } ffmpeg_info_t; static DB_fileinfo_t * ffmpeg_init (DB_playItem_t *it) { DB_fileinfo_t *_info = malloc (sizeof (ffmpeg_info_t)); ffmpeg_info_t *info = (ffmpeg_info_t*)_info; memset (info, 0, sizeof (ffmpeg_info_t)); // 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(&info->fctx, uri, NULL, 0, NULL)) < 0) { trace ("info->fctx is %p, ret %d/%s\n", info->fctx, ret, strerror(-ret)); plugin.free (_info); return NULL; } info->stream_id = -1; av_find_stream_info(info->fctx); for (i = 0; i < info->fctx->nb_streams; i++) { info->ctx = info->fctx->streams[i]->codec; if (info->ctx->codec_type == CODEC_TYPE_AUDIO) { info->codec = avcodec_find_decoder (info->ctx->codec_id); if (info->codec != NULL) { info->stream_id = i; break; } } } if (info->codec == NULL) { trace ("ffmpeg can't decode %s\n", it->fname); plugin.free (_info); return NULL; } trace ("ffmpeg can decode %s\n", it->fname); trace ("ffmpeg: codec=%s, stream=%d\n", info->codec->name, i); if (avcodec_open (info->ctx, info->codec) < 0) { trace ("ffmpeg: avcodec_open failed\n"); plugin.free (_info); return NULL; } int bps = av_get_bits_per_sample_format (info->ctx->sample_fmt); int samplerate = info->ctx->sample_rate; float duration = info->fctx->duration / (float)AV_TIME_BASE; trace ("ffmpeg: bits per sample is %d\n", bps); trace ("ffmpeg: samplerate is %d\n", samplerate); trace ("ffmpeg: duration is %lld/%fsec\n", info->fctx->duration, duration); int totalsamples = info->fctx->duration * samplerate / AV_TIME_BASE; info->left_in_packet = 0; info->left_in_buffer = 0; memset (&info->pkt, 0, sizeof (info->pkt)); info->have_packet = 0; int err = posix_memalign ((void **)&info->buffer, 16, AVCODEC_MAX_AUDIO_FRAME_SIZE); if (err) { fprintf (stderr, "ffmpeg: failed to allocate buffer memory\n"); plugin.free (_info); return NULL; } // fill in mandatory plugin fields _info->plugin = &plugin; _info->readpos = 0; _info->bps = bps; _info->channels = info->ctx->channels; _info->samplerate = samplerate; // subtrack info info->currentsample = 0; if (it->endsample > 0) { info->startsample = it->startsample; info->endsample = it->endsample; plugin.seek_sample (_info, 0); } else { info->startsample = 0; info->endsample = totalsamples - 1; } return _info; } static void ffmpeg_free (DB_fileinfo_t *_info) { ffmpeg_info_t *info = (ffmpeg_info_t*)_info; if (info) { if (info->buffer) { free (info->buffer); } // free everything allocated in _init and _read_int16 if (info->have_packet) { av_free_packet (&info->pkt); } if (info->fctx) { av_close_input_file (info->fctx); } if (info->ctx) { avcodec_close (info->ctx); } free (info); } } static int ffmpeg_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) { ffmpeg_info_t *info = (ffmpeg_info_t*)_info; // try decode `size' bytes // return number of decoded bytes // return 0 on EOF if (info->currentsample + size / (2 * _info->channels) > info->endsample) { size = (info->endsample - info->currentsample + 1) * 2 * _info->channels; if (size <= 0) { return 0; } } int initsize = size; int encsize = 0; int decsize = 0; while (size > 0) { if (info->left_in_buffer > 0) { int sz = min (size, info->left_in_buffer); memcpy (bytes, info->buffer, sz); if (sz != info->left_in_buffer) { memmove (info->buffer, info->buffer+sz, info->left_in_buffer-sz); } info->left_in_buffer -= sz; size -= sz; bytes += sz; } while (info->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 (info->ctx, (int16_t *)info->buffer, &out_size, info->pkt.data, info->pkt.size); #else len = avcodec_decode_audio3 (info->ctx, (int16_t *)info->buffer, &out_size, &info->pkt); #endif // trace ("out: out_size=%d, len=%d\n", out_size, len); if (len <= 0) { break; } encsize += len; decsize += out_size; info->left_in_packet -= len; info->left_in_buffer = out_size; } if (size == 0) { break; } // read next packet if (info->have_packet) { av_free_packet (&info->pkt); info->have_packet = 0; } int errcount = 0; for (;;) { int ret; if ((ret = av_read_frame (info->fctx, &info->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 (info->pkt.stream_index != info->stream_id) { av_free_packet (&info->pkt); continue; } // trace ("got packet: size=%d\n", pkt.size); info->have_packet = 1; info->left_in_packet = info->pkt.size; if (info->pkt.duration > 0) { AVRational *time_base = &info->fctx->streams[info->stream_id]->time_base; float sec = (float)info->pkt.duration * time_base->num / time_base->den; int bitrate = info->pkt.size/sec; if (bitrate > 0) { // FIXME: seems like duration translation is wrong deadbeef->streamer_set_bitrate (bitrate / 100); } } break; } if (!info->have_packet) { break; } } info->currentsample += (initsize-size) / (2 * _info->channels); _info->readpos = (float)info->currentsample / _info->samplerate; return initsize-size; } static int ffmpeg_seek_sample (DB_fileinfo_t *_info, int sample) { ffmpeg_info_t *info = (ffmpeg_info_t*)_info; // seek to specified sample (frame) // return 0 on success // return -1 on failure if (info->have_packet) { av_free_packet (&info->pkt); info->have_packet = 0; } sample += info->startsample; int64_t tm = (int64_t)sample/ _info->samplerate * AV_TIME_BASE; trace ("ffmpeg: seek to sample: %d, t: %d\n", sample, (int)tm); info->left_in_packet = 0; info->left_in_buffer = 0; if (av_seek_frame (info->fctx, -1, tm, AVSEEK_FLAG_ANY) < 0) { trace ("ffmpeg: seek error\n"); return -1; } // update readpos info->currentsample = sample; _info->readpos = (float)(sample - info->startsample) / _info->samplerate; return 0; } static int ffmpeg_seek (DB_fileinfo_t *_info, float time) { ffmpeg_info_t *info = (ffmpeg_info_t*)_info; // seek to specified time in seconds // return 0 on success // return -1 on failure return ffmpeg_seek_sample (_info, time * _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; } av_find_stream_info(fctx); for (i = 0; i < fctx->nb_streams; i++) { ctx = fctx->streams[i]->codec; if (ctx->codec_type == CODEC_TYPE_AUDIO) { codec = avcodec_find_decoder(ctx->codec_id); if (codec != NULL) break; } } // AVStream *stream = fctx->streams[i]; 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 = fctx->duration / (float)AV_TIME_BASE; // float duration = stream->duration * stream->time_base.num / (float)stream->time_base.den; 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; // find filetype const char *filetype; const char *ext = fname + strlen(fname) - 1; while (ext > fname && *ext != '.') { ext--; } if (*ext == '.') { ext++; } if (!strcasecmp (ext, "m4a")) { filetype = filetypes[FT_M4A]; } else if (!strcasecmp (ext, "mpc") || !strcasecmp (ext, "mp+") || !strcasecmp (ext, "mpp")) { filetype = filetypes[FT_MUSEPACK]; } else if (!strcasecmp (ext, "wma")) { filetype = filetypes[FT_WMA]; } else if (!strcasecmp (ext, "shn")) { filetype = filetypes[FT_SHORTEN]; } else if (!strcasecmp (ext, "aa3") || !strcasecmp (ext, "oma") || !strcasecmp (ext, "ac3")) { filetype = filetypes[FT_ATRAC3]; } else if (!strcasecmp (ext, "vqf")) { filetype = filetypes[FT_VQF]; } else if (!strcasecmp (ext, "tta")) { filetype = filetypes[FT_TTA]; } else { filetype = filetypes[FT_UNKNOWN]; } DB_playItem_t *it = deadbeef->pl_item_alloc (); it->decoder_id = deadbeef->plug_get_decoder_id (plugin.plugin.id); it->fname = strdup (fname); it->filetype = filetype; 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, "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); // external cuesheet DB_playItem_t *cue = deadbeef->pl_insert_cue (after, it, totalsamples, samplerate); if (cue) { deadbeef->pl_item_unref (it); return cue; } // now the track is ready, insert into playlist after = deadbeef->pl_insert_item (after, it); deadbeef->pl_item_unref (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) { trace ("ffmpeg_vfs_close\n"); 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.id = "ffmpeg", .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, .filetypes = filetypes }; DB_plugin_t * ffmpeg_load (DB_functions_t *api) { deadbeef = api; return DB_PLUGIN (&plugin); }