diff options
author | Viliam Mateicka <viliam.mateicka@gmail.com> | 2008-10-17 22:27:33 +0200 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2008-10-17 22:27:33 +0200 |
commit | 11ad9971415c18dfb8f2042b65da31d0102b8c91 (patch) | |
tree | 96cf617ba830f1c4037f10bd6bdf1eb8135f405f | |
parent | 4ee8da2e694d29efef5f6311d3bf8504d474918f (diff) | |
download | mpd-11ad9971415c18dfb8f2042b65da31d0102b8c91.tar.gz mpd-11ad9971415c18dfb8f2042b65da31d0102b8c91.tar.xz mpd-11ad9971415c18dfb8f2042b65da31d0102b8c91.zip |
ffmpeg: new decoder plugin
[mk: fixed indent, changed copyright statement, added autoconf test,
fixed includes paths, fixed 2 gcc warnings, don't close input stream
twice]
-rw-r--r-- | configure.ac | 21 | ||||
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/decoder_list.c | 4 | ||||
-rw-r--r-- | src/inputPlugins/ffmpeg_plugin.c | 416 |
4 files changed, 447 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac index a5cee2ca3..8adebafa0 100644 --- a/configure.ac +++ b/configure.ac @@ -234,6 +234,12 @@ AC_ARG_ENABLE(wavpack, enable_wavpack=$enableval, enable_wavpack=yes) +AC_ARG_ENABLE(ffmpeg, + AS_HELP_STRING([--disable-ffmpeg], + [enable FFMPEG support (default: enable)]), + enable_ffmpeg=$enableval, + enable_ffmpeg=yes) + AC_ARG_ENABLE(id3, AS_HELP_STRING([--disable-id3], [disable id3 support (default: enable)]), @@ -672,6 +678,14 @@ fi AM_CONDITIONAL(HAVE_MIKMOD, test x$enable_mod = xyes) +if test x$enable_ffmpeg = xyes; then + PKG_CHECK_MODULES(FFMPEG, [libavcodec libavformat], + AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support]), + enable_ffmpeg=no) +fi + +AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes) + case $with_zeroconf in no|avahi|bonjour) ;; @@ -955,6 +969,12 @@ else echo " MOD support ...................disabled" fi +if test x$enable_ffmpeg = xyes; then + echo " FFMPEG support ................enabled" +else + echo " FFMPEG support ................disabled" +fi + if test x$enable_mp3 = xno && test x$enable_oggvorbis = xno && @@ -964,6 +984,7 @@ if test x$enable_aac = xno && test x$enable_mpc = xno && test x$enable_wavpack = xno && + test x$enable_ffmpeg = xno && test x$enable_mod = xno; then AC_MSG_ERROR([No input plugins supported!]) fi diff --git a/src/Makefile.am b/src/Makefile.am index 70222db0c..56360812a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -225,6 +225,10 @@ if HAVE_MIKMOD mpd_SOURCES += inputPlugins/mod_plugin.c endif +if HAVE_FFMPEG +mpd_SOURCES += inputPlugins/ffmpeg_plugin.c +endif + if HAVE_ZEROCONF mpd_SOURCES += zeroconf.c @@ -240,6 +244,7 @@ mpd_CPPFLAGS = \ $(AUDIOFILE_CFLAGS) $(LIBMIKMOD_CFLAGS) \ $(ID3TAG_CFLAGS) \ $(MAD_CFLAGS) \ + $(FFMPEG_CFLAGS) \ $(GLIB_CFLAGS) mpd_LDADD = $(MPD_LIBS) $(MP4FF_LIB) \ $(AO_LIBS) $(ALSA_LIBS) \ @@ -248,6 +253,7 @@ mpd_LDADD = $(MPD_LIBS) $(MP4FF_LIB) \ $(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \ $(ID3TAG_LIBS) \ $(MAD_LIBS) \ + $(FFMPEG_LIBS) \ $(GLIB_LIBS) DIST_SUBDIRS = mp4ff diff --git a/src/decoder_list.c b/src/decoder_list.c index 4aab73bda..252418154 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -30,6 +30,7 @@ extern struct decoder_plugin aacPlugin; extern struct decoder_plugin mpcPlugin; extern struct decoder_plugin wavpackPlugin; extern struct decoder_plugin modPlugin; +extern struct decoder_plugin ffmpegPlugin; static List *inputPlugin_list; @@ -179,6 +180,9 @@ void decoder_plugin_init_all(void) #ifdef HAVE_MIKMOD decoder_plugin_load(&modPlugin); #endif +#ifdef HAVE_FFMPEG + decoder_plugin_load(&ffmpegPlugin); +#endif } void decoder_plugin_deinit_all(void) diff --git a/src/inputPlugins/ffmpeg_plugin.c b/src/inputPlugins/ffmpeg_plugin.c new file mode 100644 index 000000000..a9b69f298 --- /dev/null +++ b/src/inputPlugins/ffmpeg_plugin.c @@ -0,0 +1,416 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2008 Viliam Mateicka <viliam.mateicka@gmail.com> + * This project's homepage is: http://www.musicpd.org + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "../decoder_api.h" +#include "../log.h" +#include "../utils.h" +#include "../log.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <avcodec.h> +#include <avformat.h> +#include <avio.h> + +typedef struct { + int audioStream; + AVFormatContext *pFormatCtx; + AVCodecContext *aCodecCtx; + AVCodec *aCodec; + struct decoder *decoder; + InputStream *input; + struct tag *tag; +} BasePtrs; + +typedef struct { + struct decoder *decoder; + InputStream *input; +} FopsHelper; + +int mpdurl_open(URLContext *h, const char *filename, int flags); +int mpdurl_read(URLContext *h, unsigned char *buf, int size); +int64_t mpdurl_seek(URLContext *h, int64_t pos, int whence); +int mpdurl_close(URLContext *h); + +URLProtocol mpdurl_fileops = { + .name = "mpd", + .url_open = mpdurl_open, + .url_read = mpdurl_read, + .url_write = NULL, + .url_seek = mpdurl_seek, + .url_close = mpdurl_close, + .next = NULL, + .url_read_pause = NULL, + .url_read_seek = NULL +}; + + +int mpdurl_open(URLContext *h, const char *filename, int flags) +{ + uint32_t ptr; + FopsHelper *base; + if (strlen(filename) == (8+8)) { + errno = 0; + ptr = (uint32_t) strtoll(filename + 6, NULL, 16); + if (errno == 0 && ptr != 0) { + base = (FopsHelper *) ptr; + h->priv_data = base; + h->is_streamed = (base->input->seekable ? 0 : 1); + return 0; + } + } + FATAL("Invalid format %s:%d\n", filename, flags); + return -1; +} + +int mpdurl_read(URLContext *h, unsigned char *buf, int size) +{ + int ret; + FopsHelper *base = (FopsHelper *) h->priv_data; + while (1) { + ret = readFromInputStream(base->input, (void *)buf, size); + if (ret == 0) { + DEBUG("ret 0\n"); + if (inputStreamAtEOF(base->input) || + (base->decoder && + decoder_get_command(base->decoder) != DECODE_COMMAND_NONE)) { + DEBUG("eof stream\n"); + return ret; + } else { + my_usleep(10000); + } + } else { + break; + } + } + return ret; +} + +int64_t mpdurl_seek(URLContext *h, int64_t pos, int whence) +{ + FopsHelper *base = (FopsHelper *) h->priv_data; + if (whence != AVSEEK_SIZE) { //only ftell + (void) seekInputStream(base->input, pos, whence); + } + return base->input->offset; +} + +int mpdurl_close(URLContext *h) +{ + FopsHelper *base = (FopsHelper *) h->priv_data; + if (base && base->input->seekable) { + (void) seekInputStream(base->input, 0, SEEK_SET); + } + h->priv_data = 0; + return 0; +} + +static int ffmpeg_init(void) +{ + av_register_all(); + register_protocol(&mpdurl_fileops); + return 0; +} + +static int ffmpeg_helper(InputStream *input, int (*callback)(BasePtrs *ptrs), + BasePtrs *ptrs) +{ + AVFormatContext *pFormatCtx; + AVCodecContext *aCodecCtx; + AVCodec *aCodec; + int ret, audioStream; + unsigned i; + FopsHelper fopshelp; + char url[24]; + + fopshelp.input = input; + if (ptrs && ptrs->decoder) { + fopshelp.decoder = ptrs->decoder; //are we in decoding loop ? + } else { + fopshelp.decoder = NULL; + } + + //ffmpeg works with ours "fileops" helper + sprintf(url, "mpd://0x%8x", (unsigned int) &fopshelp); + + if (av_open_input_file(&pFormatCtx, url, NULL, 0, NULL)!=0) { + ERROR("Open failed!\n"); + return -1; + } + + if (av_find_stream_info(pFormatCtx)<0) { + ERROR("Couldn't find stream info!\n"); + return -1; + } + + audioStream = -1; + for(i=0; i<pFormatCtx->nb_streams; i++) { + if (pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO && + audioStream < 0) { + audioStream=i; + } + } + + if(audioStream==-1) { + ERROR("No audio stream inside!\n"); + return -1; + } + + aCodecCtx = pFormatCtx->streams[audioStream]->codec; + aCodec = avcodec_find_decoder(aCodecCtx->codec_id); + + if (!aCodec) { + ERROR("Unsupported audio codec!\n"); + return -1; + } + + if (avcodec_open(aCodecCtx, aCodec)<0) { + ERROR("Could not open codec!\n"); + return -1; + } + + if (callback) { + ptrs->audioStream = audioStream; + ptrs->pFormatCtx = pFormatCtx; + ptrs->aCodecCtx = aCodecCtx; + ptrs->aCodec = aCodec; + + ret = (*callback)( ptrs ); + } else { + ret = 0; + DEBUG("playable\n"); + } + + avcodec_close(aCodecCtx); + av_close_input_file(pFormatCtx); + + return ret; +} + +static bool ffmpeg_try_decode(InputStream *input) +{ + int ret; + if (input->seekable) { + ret = ffmpeg_helper(input, NULL, NULL); + } else { + ret = 0; + } + return (ret == -1 ? 0 : 1); +} + +static int ffmpeg_decode_internal(BasePtrs *base) +{ + struct decoder *decoder = base->decoder; + AVCodecContext *aCodecCtx = base->aCodecCtx; + AVFormatContext *pFormatCtx = base->pFormatCtx; + AVPacket packet; + int len, audio_size; + int position; + struct audio_format audio_format; + int current, total_time; + uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2]; + + total_time = 0; + + DEBUG("decoder_start\n"); + + if (aCodecCtx->channels > 2) { + aCodecCtx->channels = 2; + } + + audio_format.bits = (uint8_t)16; + audio_format.sample_rate = (unsigned int)aCodecCtx->sample_rate; + audio_format.channels = aCodecCtx->channels; + + // frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); + // total_time = ((float)frame_count / (float)audio_format.sample_rate); + + //there is some problem with this on some demux (mp3 at least) + if (pFormatCtx->duration != (int)AV_NOPTS_VALUE) { + total_time = pFormatCtx->duration / AV_TIME_BASE; + } + + DEBUG("ffmpeg sample rate: %dHz %d channels\n", + aCodecCtx->sample_rate, aCodecCtx->channels); + + decoder_initialized(decoder, &audio_format, total_time); + + position = 0; + + DEBUG("duration:%d (%d secs)\n", (int) pFormatCtx->duration, + (int) total_time); + + do { + + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + + DEBUG("seek\n"); + decoder_clear(decoder); + current = decoder_seek_where(decoder) * AV_TIME_BASE; + + if (av_seek_frame(pFormatCtx, -1, current , 0) < 0) { + WARNING("seek to %d failed\n", current); + } + + decoder_command_finished(decoder); + } + + if (av_read_frame(pFormatCtx, &packet) >= 0) { + if(packet.stream_index == base->audioStream) { + + position = av_rescale_q(packet.pts, pFormatCtx->streams[base->audioStream]->time_base, + (AVRational){1, 1}); + + audio_size = sizeof(audio_buf); + len = avcodec_decode_audio2(aCodecCtx, + (int16_t *)audio_buf, + &audio_size, + packet.data, + packet.size); + + if(len >= 0) { + if(audio_size >= 0) { + // DEBUG("sending data %d/%d\n", audio_size, len); + + decoder_data(decoder, NULL, 1, + audio_buf, audio_size, + position, //(float)current / (float)audio_format.sample_rate, + aCodecCtx->bit_rate / 1000, NULL); + + } + } else { + WARNING("skiping frame!\n"); + } + } + av_free_packet(&packet); + } else { + //end of file + break; + } + } while (decoder_get_command(decoder) != DECODE_COMMAND_STOP); + + decoder_flush(decoder); + + DEBUG("decoder finish\n"); + + return 0; +} + +static int ffmpeg_decode(struct decoder *decoder, InputStream *input) +{ + BasePtrs base; + int ret; + + DEBUG("decode start\n"); + + base.input = input; + base.decoder = decoder; + + ret = ffmpeg_helper(input, ffmpeg_decode_internal, &base); + + DEBUG("decode finish\n"); + + return ret; +} + +static int ffmpeg_tag_internal(BasePtrs *base) +{ + struct tag *tag = (struct tag *) base->tag; + + if (base->pFormatCtx->duration != (int)AV_NOPTS_VALUE) { + tag->time = base->pFormatCtx->duration / AV_TIME_BASE; + } else { + tag->time = 0; + } + return 0; +} + +//no tag reading in ffmpeg, check if playable +static struct tag *ffmpeg_tag(char *file) +{ + InputStream input; + BasePtrs base; + int ret; + struct tag *tag = NULL; + + if (openInputStream(&input, file) < 0) { + ERROR("failed to open %s\n", file); + return NULL; + } + + tag = tag_new(); + + base.tag = tag; + ret = ffmpeg_helper(&input, ffmpeg_tag_internal, &base); + + if (ret != 0) { + free(tag); + tag = NULL; + } + + closeInputStream(&input); + + return tag; +} + +/** + * ffmpeg can decode almost everything from open codecs + * and also some of propietary codecs + * its hard to tell what can ffmpeg decode + * we can later put this into configure script + * to be sure ffmpeg is used to handle + * only that files + */ + +static const char *ffmpeg_Suffixes[] = { + "wma", "asf", "wmv", "mpeg", "mpg", "avi", "vob", "mov", "qt", "swf", "rm", "swf", + "mp1", "mp2", "mp3", "mp4", "m4a", "flac", "ogg", "wav", "au", "aiff", "aif", "ac3", "aac", "mpc", + NULL +}; + +//not sure if this is correct... +static const char *ffmpeg_Mimetypes[] = { + "video/x-ms-asf", + "audio/x-ms-wma", + "audio/x-ms-wax", + "video/x-ms-wmv", + "video/x-ms-wvx", + "video/x-ms-wm", + "video/x-ms-wmx", + "application/x-ms-wmz", + "application/x-ms-wmd", + "audio/mpeg", + NULL +}; + +struct decoder_plugin ffmpegPlugin = { + .name = "ffmpeg", + .init = ffmpeg_init, + .try_decode = ffmpeg_try_decode, + .stream_decode = ffmpeg_decode, + .tag_dup = ffmpeg_tag, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = ffmpeg_Suffixes, + .mime_types = ffmpeg_Mimetypes +}; |