/* DeaDBeeF - The Ultimate Music Player Copyright (C) 2009-2013 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" #include "../../strdupa.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 #ifndef AV_VERSION_INT #define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c) #endif #endif #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) #define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 #define av_find_stream_info(ctx) avformat_find_stream_info(ctx,NULL) #define avcodec_open(ctx,codec) avcodec_open2(ctx,codec,NULL) #define av_get_bits_per_sample_format(fmt) (av_get_bytes_per_sample(fmt)*8) #define av_close_input_file(ctx) avformat_close_input(&(ctx)) #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; #define DEFAULT_EXTS "aa3;oma;ac3;vqf;amr;opus;tak" #define EXT_MAX 100 #define FFMPEG_MAX_ANALYZE_DURATION 500000 static char * exts[EXT_MAX] = {NULL}; enum { FT_ALAC = 0, FT_WMA = 1, FT_ATRAC3 = 2, FT_VQF = 3, FT_AC3 = 4, FT_AMR = 5, FT_UNKNOWN = 5 }; #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(54, 6, 0) #define FF_PROTOCOL_NAME "deadbeef" #endif typedef struct { DB_fileinfo_t info; AVCodec *codec; AVCodecContext *ctx; AVFormatContext *fctx; AVPacket pkt; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 40, 0) AVFrame *frame; #endif 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_playItem_t *current_track; static DB_fileinfo_t *current_info; static DB_fileinfo_t * ffmpeg_open (uint32_t hints) { DB_fileinfo_t *_info = malloc (sizeof (ffmpeg_info_t)); memset (_info, 0, sizeof (ffmpeg_info_t)); return _info; } static int ffmpeg_init (DB_fileinfo_t *_info, DB_playItem_t *it) { ffmpeg_info_t *info = (ffmpeg_info_t *)_info; trace ("ffmpeg init %s\n", deadbeef->pl_find_meta (it, ":URI")); // prepare to decode the track // return -1 on failure int ret; char *uri = NULL; int i; deadbeef->pl_lock (); { const char *fname = deadbeef->pl_find_meta (it, ":URI"); #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) uri = strdupa (fname); #else int l = strlen (fname); uri = alloca (l + sizeof (FF_PROTOCOL_NAME) + 1); // 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; #endif } deadbeef->pl_unlock (); trace ("ffmpeg: uri: %s\n", uri); // open file trace ("\033[0;31mffmpeg av_open_input_file\033[37;0m\n"); current_track = it; current_info = _info; #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) avformat_network_init(); info->fctx = avformat_alloc_context (); if ((ret = avformat_open_input(&info->fctx, uri, NULL, NULL)) < 0) { #else if ((ret = av_open_input_file(&info->fctx, uri, NULL, 0, NULL)) < 0) { #endif current_track = NULL; trace ("\033[0;31minfo->fctx is %p, ret %d/%s\033[0;31m\n", info->fctx, ret, strerror(-ret)); return -1; } trace ("\033[0;31mav_open_input_file done, ret=%d\033[0;31m\n", ret); current_track = NULL; current_info = NULL; trace ("\033[0;31mffmpeg av_find_stream_info\033[37;0m\n"); info->stream_id = -1; info->fctx->max_analyze_duration = FFMPEG_MAX_ANALYZE_DURATION; 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 == #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0) AVMEDIA_TYPE_AUDIO) #else CODEC_TYPE_AUDIO) #endif { 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", deadbeef->pl_find_meta (it, ":URI")); return -1; } trace ("ffmpeg can decode %s\n", deadbeef->pl_find_meta (it, ":URI")); 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"); return -1; } deadbeef->pl_replace_meta (it, "!FILETYPE", info->codec->name); 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"); return -1; } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 40, 0) info->frame = avcodec_alloc_frame(); #endif // fill in mandatory plugin fields _info->plugin = &plugin; _info->readpos = 0; _info->fmt.bps = bps; _info->fmt.channels = info->ctx->channels; _info->fmt.samplerate = samplerate; if (info->ctx->sample_fmt == AV_SAMPLE_FMT_FLT || info->ctx->sample_fmt == AV_SAMPLE_FMT_FLTP) { _info->fmt.is_float = 1; } int64_t layout = info->ctx->channel_layout; if (layout != 0, 0) { _info->fmt.channelmask = layout; } else { for (int i = 0; i < _info->fmt.channels; i++) { _info->fmt.channelmask |= 1 << i; } } // 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 0; } static void ffmpeg_free (DB_fileinfo_t *_info) { trace ("ffmpeg: free\n"); ffmpeg_info_t *info = (ffmpeg_info_t*)_info; if (info) { #if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(54, 59, 100) if (info->frame) { avcodec_free_frame(&info->frame); } #elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 40, 0) if (info->frame) { av_freep (&info->frame); } #endif if (info->buffer) { free (info->buffer); } // free everything allocated in _init and _read if (info->have_packet) { av_free_packet (&info->pkt); } if (info->ctx) { avcodec_close (info->ctx); } if (info->fctx) { av_close_input_file (info->fctx); } free (info); } } static int ffmpeg_read (DB_fileinfo_t *_info, char *bytes, int size) { trace ("ffmpeg_read_int16 %d\n", size); ffmpeg_info_t *info = (ffmpeg_info_t*)_info; _info->fmt.channels = info->ctx->channels; _info->fmt.samplerate = info->ctx->sample_rate; _info->fmt.bps = av_get_bits_per_sample_format (info->ctx->sample_fmt); _info->fmt.is_float = (info->ctx->sample_fmt == AV_SAMPLE_FMT_FLT || info->ctx->sample_fmt == AV_SAMPLE_FMT_FLTP); int samplesize = _info->fmt.channels * _info->fmt.bps / 8; if (info->endsample >= 0 && info->currentsample + size / samplesize > info->endsample) { size = (info->endsample - info->currentsample + 1) * samplesize; 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); int nsamples = size / samplesize; int nsamples_buf = info->left_in_buffer / samplesize; nsamples = min (nsamples, nsamples_buf); int sz = nsamples * samplesize; memcpy (bytes, info->buffer, nsamples*samplesize); bytes += nsamples * samplesize; size -= nsamples * samplesize; if (sz != info->left_in_buffer) { memmove (info->buffer, info->buffer+sz, info->left_in_buffer-sz); } info->left_in_buffer -= 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_INT >= AV_VERSION_INT(53, 40, 0) int got_frame = 0; len = avcodec_decode_audio4(info->ctx, info->frame, &got_frame, &info->pkt); if (len > 0) { if (av_sample_fmt_is_planar(info->ctx->sample_fmt)) { out_size = 0; for (int c = 0; c < info->ctx->channels; c++) { for (int i = 0; i < info->frame->nb_samples; i++) { if (_info->fmt.bps == 8) { info->buffer[i*info->ctx->channels+c] = ((int8_t *)info->frame->extended_data[c])[i]; out_size++; } else if (_info->fmt.bps == 16) { int16_t outsample = ((int16_t *)info->frame->extended_data[c])[i]; ((int16_t*)info->buffer)[i*info->ctx->channels+c] = outsample; out_size += 2; } else if (_info->fmt.bps == 24) { memcpy (&info->buffer[(i*info->ctx->channels+c)*3], &((int8_t*)info->frame->extended_data[c])[i*3], 3); out_size += 3; } else if (_info->fmt.bps == 32) { int32_t sample = ((int32_t *)info->frame->extended_data[c])[i]; ((int32_t*)info->buffer)[i*info->ctx->channels+c] = sample; out_size += 4; } } } } else { out_size = info->frame->nb_samples * (_info->fmt.bps >> 3) * _info->fmt.channels; memcpy (info->buffer, info->frame->extended_data[0], out_size); } } #elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) len = avcodec_decode_audio3 (info->ctx, (int16_t *)info->buffer, &out_size, &info->pkt); #else len = avcodec_decode_audio2 (info->ctx, (int16_t *)info->buffer, &out_size, info->pkt.data, info->pkt.size); #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 { trace ("av packet size: %d, numframes: %d\n", info->pkt.size, ret); errcount = 0; } if (ret == -1) { break; } //trace ("idx:%d, stream:%d\n", info->pkt.stream_index, info->stream_id); if (info->pkt.stream_index != info->stream_id) { av_free_packet (&info->pkt); continue; } //trace ("got packet: size=%d\n", info->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 * 8 / sec; if (bitrate > 0) { deadbeef->streamer_set_bitrate (bitrate / 1000); } } break; } if (!info->have_packet) { break; } } info->currentsample += (initsize-size) / samplesize; _info->readpos = (float)info->currentsample / _info->fmt.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->fmt.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->fmt.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->fmt.samplerate); } static const char *map[] = { "artist", "artist", "title", "title", "album", "album", "track", "track", "date", "year", "WM/Year", "year", "genre", "genre", "comment", "comment", "performer", "performer", "album_artist", "band", "composer", "composer", "encoder", "encoder", "encoded_by", "vendor", "disc", "disc", "copyright", "copyright", "tracktotal", "numtracks", "publisher", "publisher", NULL }; static int ffmpeg_read_metadata_internal (DB_playItem_t *it, AVFormatContext *fctx) { #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52,43,0) 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 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,23,0) AVMetadata *md = fctx->metadata; for (int m = 0; map[m]; m += 2) { AVMetadataTag *tag = NULL; do { tag = av_metadata_get (md, map[m], tag, AV_METADATA_DONT_STRDUP_KEY | AV_METADATA_DONT_STRDUP_VAL); if (tag) { deadbeef->pl_append_meta (it, map[m+1], tag->value); } } while (tag); } #else // ffmpeg-0.11 new metadata format AVDictionaryEntry *t = NULL; int m; for (int i = 0; i < fctx->nb_streams + 1; i++) { AVDictionary *md = i == 0 ? fctx->metadata : fctx->streams[i-1]->metadata; if (!md) { continue; } while (t = av_dict_get (md, "", t, AV_DICT_IGNORE_SUFFIX)) { if (!strcasecmp (t->key, "replaygain_album_gain")) { deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, atof (t->value)); continue; } else if (!strcasecmp (t->key, "replaygain_album_peak")) { deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, atof (t->value)); continue; } else if (!strcasecmp (t->key, "replaygain_track_gain")) { deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, atof (t->value)); continue; } else if (!strcasecmp (t->key, "replaygain_track_peak")) { deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, atof (t->value)); continue; } for (m = 0; map[m]; m += 2) { if (!strcasecmp (t->key, map[m])) { deadbeef->pl_append_meta (it, map[m+1], t->value); break; } } if (!map[m]) { deadbeef->pl_append_meta (it, t->key, t->value); } } } #endif #endif return 0; } static DB_playItem_t * ffmpeg_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) { trace ("ffmpeg_insert %s\n", 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 = NULL; int i; // construct uri #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) uri = strdupa (fname); #else uri = alloca (l + sizeof (FF_PROTOCOL_NAME) + 1); 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; #endif trace ("ffmpeg: uri: %s\n", uri); // open file #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) avformat_network_init(); fctx = avformat_alloc_context (); if ((ret = avformat_open_input(&fctx, uri, NULL, NULL)) < 0) { #else if ((ret = av_open_input_file(&fctx, uri, NULL, 0, NULL)) < 0) { #endif trace ("fctx is %p, ret %d/%s", fctx, ret, strerror(-ret)); return NULL; } trace ("fctx is %p, ret %d/%s\n", fctx, ret, strerror(-ret)); fctx->max_analyze_duration = FFMPEG_MAX_ANALYZE_DURATION; #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) int nb_streams = fctx->nb_streams; ret = avformat_find_stream_info(fctx, NULL); #else ret = av_find_stream_info(fctx); int nb_streams = fctx->nb_streams; #endif if (ret < 0) { trace ("av_find_stream_info ret: %d/%s\n", ret, strerror(-ret)); } trace ("nb_streams=%x\n", nb_streams); for (i = 0; i < fctx->nb_streams; i++) { if (!fctx->streams[i]) { continue; } ctx = fctx->streams[i]->codec; if (ctx->codec_type == #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0) AVMEDIA_TYPE_AUDIO) #else CODEC_TYPE_AUDIO) #endif { 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; DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id); deadbeef->pl_replace_meta (it, ":FILETYPE", codec->name); if (!deadbeef->is_local_file (fname)) { deadbeef->plt_set_item_duration (plt, it, -1); } else { deadbeef->plt_set_item_duration (plt, it, duration); } // add metainfo ffmpeg_read_metadata_internal (it, fctx); int64_t fsize = -1; DB_FILE *fp = deadbeef->fopen (fname); if (fp) { if (!fp->vfs->is_streaming ()) { fsize = deadbeef->fgetlength (fp); } deadbeef->fclose (fp); } if (fsize >= 0 && duration > 0) { char s[100]; snprintf (s, sizeof (s), "%lld", fsize); deadbeef->pl_add_meta (it, ":FILE_SIZE", s); snprintf (s, sizeof (s), "%d", av_get_bits_per_sample_format (ctx->sample_fmt)); deadbeef->pl_add_meta (it, ":BPS", s); snprintf (s, sizeof (s), "%d", ctx->channels); deadbeef->pl_add_meta (it, ":CHANNELS", s); snprintf (s, sizeof (s), "%d", samplerate); deadbeef->pl_add_meta (it, ":SAMPLERATE", s); int br = (int)roundf(fsize / duration * 8 / 1000); snprintf (s, sizeof (s), "%d", br); deadbeef->pl_add_meta (it, ":BITRATE", s); } // free decoder avcodec_close (ctx); av_close_input_file(fctx); // external cuesheet DB_playItem_t *cue = deadbeef->plt_insert_cue (plt, after, it, totalsamples, samplerate); if (cue) { deadbeef->pl_item_unref (it); deadbeef->pl_item_unref (cue); return cue; } // now the track is ready, insert into playlist after = deadbeef->plt_insert_item (plt, after, it); deadbeef->pl_item_unref (it); return after; } #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(54, 6, 0) // vfs wrapper for ffmpeg, can't only be done with ffmpeg<0.11 static int ffmpeg_vfs_open(URLContext *h, const char *filename, int flags) { DB_FILE *f; av_strstart(filename, FF_PROTOCOL_NAME ":", &filename); #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(54, 6, 0) if (flags & URL_WRONLY) { return -ENOENT; } else #endif { f = deadbeef->fopen (filename); } if (f == NULL) return -ENOENT; if (f->vfs->is_streaming ()) { deadbeef->fset_track (f, current_track); if (current_info) { current_info->file = f; } } h->priv_data = f; return 0; } static int ffmpeg_vfs_read(URLContext *h, unsigned char *buf, int size) { trace ("ffmpeg_vfs_read %d\n", size); int res = deadbeef->fread (buf, 1, size, h->priv_data); return res; } static int ffmpeg_vfs_write(URLContext *h, const unsigned char *buf, int size) { return -1; } static int64_t ffmpeg_vfs_seek(URLContext *h, int64_t pos, int whence) { trace ("ffmpeg_vfs_seek %d %d\n", pos, whence); DB_FILE *f = h->priv_data; if (whence == AVSEEK_SIZE) { return f->vfs->is_streaming () ? -1 : deadbeef->fgetlength (h->priv_data); } else if (f->vfs->is_streaming ()) { return -1; } else { 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, }; #endif static void ffmpeg_init_exts (void) { deadbeef->conf_lock (); const char *new_exts = deadbeef->conf_get_str_fast ("ffmpeg.extensions", DEFAULT_EXTS); for (int i = 0; exts[i]; i++) { free (exts[i]); } exts[0] = NULL; int n = 0; while (*new_exts) { if (n >= EXT_MAX) { fprintf (stderr, "ffmpeg: too many extensions, max is %d\n", EXT_MAX); break; } const char *e = new_exts; while (*e && *e != ';') { e++; } if (e != new_exts) { char *ext = malloc (e-new_exts+1); memcpy (ext, new_exts, e-new_exts); ext[e-new_exts] = 0; exts[n++] = ext; } if (*e == 0) { break; } new_exts = e+1; } exts[n] = NULL; deadbeef->conf_unlock (); } static int ffmpeg_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) { switch (id) { case DB_EV_CONFIGCHANGED: ffmpeg_init_exts (); break; } return 0; } static int ffmpeg_start (void) { ffmpeg_init_exts (); #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(54, 6, 0) avcodec_register_all (); #endif av_register_all (); #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) #warning FFMPEG-0.11 and higher does not expose register_protocol API, which means that it cant work with MMS and HTTP plugins. If you need this functionality, please downgrade FFMPEG to version 0.10 or less, and rebuild the FFMPEG plugin #elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52, 69, 0) av_register_protocol2 (&vfswrapper, sizeof(vfswrapper)); #else av_register_protocol (&vfswrapper); #endif return 0; } static int ffmpeg_stop (void) { for (int i = 0; exts[i]; i++) { free (exts[i]); } exts[0] = NULL; return 0; } int ffmpeg_read_metadata (DB_playItem_t *it) { trace ("ffmpeg_read_metadata: fname %s\n", deadbeef->pl_find_meta (it, ":URI")); AVCodec *codec = NULL; AVCodecContext *ctx = NULL; AVFormatContext *fctx = NULL; int ret; char *uri = NULL; int i; deadbeef->pl_lock (); { const char *fname = deadbeef->pl_find_meta (it, ":URI"); #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) uri = strdupa (fname); #else int l = strlen (fname); uri = alloca (l + sizeof (FF_PROTOCOL_NAME) + 1); // 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; #endif } deadbeef->pl_unlock (); trace ("ffmpeg: uri: %s\n", uri); // open file #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 6, 0) avformat_network_init(); fctx = avformat_alloc_context (); if ((ret = avformat_open_input(&fctx, uri, NULL, NULL)) < 0) { #else if ((ret = av_open_input_file(&fctx, uri, NULL, 0, NULL)) < 0) { #endif trace ("fctx is %p, ret %d/%s", fctx, ret, strerror(-ret)); return -1; } av_find_stream_info(fctx); for (i = 0; i < fctx->nb_streams; i++) { ctx = fctx->streams[i]->codec; if (ctx->codec_type == #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0) AVMEDIA_TYPE_AUDIO) #else CODEC_TYPE_AUDIO) #endif { codec = avcodec_find_decoder(ctx->codec_id); if (codec != NULL) break; } } if (codec == NULL) { trace ("ffmpeg can't decode %s\n", deadbeef->pl_find_meta (it, ":URI")); av_close_input_file(fctx); return -1; } if (avcodec_open (ctx, codec) < 0) { trace ("ffmpeg: avcodec_open failed\n"); av_close_input_file(fctx); return -1; } deadbeef->pl_delete_all_meta (it); ffmpeg_read_metadata_internal (it, fctx); av_close_input_file(fctx); return 0; } static const char settings_dlg[] = "property \"File Extensions (separate with ';')\" entry ffmpeg.extensions \"" DEFAULT_EXTS "\";\n" ; // define plugin interface static DB_decoder_t plugin = { .plugin.api_vmajor = 1, .plugin.api_vminor = 0, .plugin.version_major = 1, .plugin.version_minor = 2, .plugin.type = DB_PLUGIN_DECODER, .plugin.id = "ffmpeg", .plugin.name = "FFMPEG audio player", .plugin.descr = "decodes audio formats using FFMPEG libavcodec", .plugin.copyright = "Copyright (C) 2009-2013 Alexey Yakovenko \n" "\n" "This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public License\n" "as published by the Free Software Foundation; either version 2\n" "of the License, or (at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" , .plugin.website = "http://deadbeef.sf.net", .plugin.start = ffmpeg_start, .plugin.stop = ffmpeg_stop, .plugin.configdialog = settings_dlg, .plugin.message = ffmpeg_message, .open = ffmpeg_open, .init = ffmpeg_init, .free = ffmpeg_free, .read = ffmpeg_read, .seek = ffmpeg_seek, .seek_sample = ffmpeg_seek_sample, .insert = ffmpeg_insert, .read_metadata = ffmpeg_read_metadata, .exts = (const char **)exts, }; DB_plugin_t * ffmpeg_load (DB_functions_t *api) { deadbeef = api; return DB_PLUGIN (&plugin); }