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]
This commit is contained in:
parent
4ee8da2e69
commit
11ad997141
21
configure.ac
21
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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
416
src/inputPlugins/ffmpeg_plugin.c
Normal file
416
src/inputPlugins/ffmpeg_plugin.c
Normal file
@ -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
|
||||
};
|
Loading…
Reference in New Issue
Block a user