diff --git a/configure.in b/configure.in index 4a72ee8b..9df13321 100644 --- a/configure.in +++ b/configure.in @@ -38,6 +38,7 @@ AC_DEFINE_UNQUOTED(CONFFILE,"${CONFFILE}",Where the config file is) rend_posix=true db_sqlite=false db_sqlite3=false +use_ffmpeg=false; STATIC_LIBS=no CPPFLAGS="${CPPFLAGS} -g -Wall" @@ -98,6 +99,8 @@ AC_ARG_ENABLE(gdbm,[ --enable-gdbm Enable gdbm support], use_gdbm=true; CPPFLAGS="${CPPFLAGS} -DGDBM") +AC_ARG_ENABLE(ffmpeg,[ --enable-ffmpeg Enable ffmpeg transcode support], + use_ffmpeg=true;) AM_CONDITIONAL(COND_REND_HOWL, test x$rend_howl = xtrue) AM_CONDITIONAL(COND_REND_POSIX, test x$rend_posix = xtrue) @@ -107,9 +110,11 @@ AM_CONDITIONAL(COND_MUSEPACK, test x$use_musepack = xtrue) AM_CONDITIONAL(COND_SQLITE,test x$db_sqlite = xtrue) AM_CONDITIONAL(COND_SQLITE3,test x$db_sqlite3 = xtrue) AM_CONDITIONAL(COND_GDBM,test x$use_gdbm = xtrue) +AM_CONDITIONAL(COND_FFMPEG,test x$use_ffmpeg = xtrue) AM_CONDITIONAL(COND_NEED_STRCASESTR,false) AM_CONDITIONAL(COND_NEED_STRSEP,false) + if test x$have_sql = xtrue; then CPPFLAGS="${CPPFLAGS} -DHAVE_SQL" fi @@ -226,6 +231,22 @@ AC_ARG_WITH(sqlite3-libs, fi ]) +AC_ARG_WITH(ffmpeg-includes, + [--with-ffmpeg-includes[[=DIR]] use ffmpeg include files in DIR],[ + if test "$withval" != "no" -a "$withval" != "yes"; then + Z_DIR=$withval + CPPFLAGS="${CPPFLAGS} -I$withval" + fi +]) + +AC_ARG_WITH(ffmpeg-libs, + [--with-ffmpeg-libs[[=DIR]] use ffmpeg lib files in DIR],[ + if test "$withval" != "no" -a "$withval" != "yes"; then + Z_DIR=$withval + LDFLAGS="${LDFLAGS} -L$withval -R$withval" + fi +]) + AC_ARG_WITH(id3tag, [--with-id3tag[[=DIR]] use id3tag in DIR],[ if test "$withval" != "no" -a "$withval" != "yes"; then @@ -330,6 +351,18 @@ if test x$use_flac = xtrue; then fi fi +if test x$use_ffmpeg = xtrue; then + AC_CHECK_HEADERS(avcodec.h,, [ + AC_MSG_ERROR([avcodec.h not found... Must have ffmpeg installed])]) +dnl AC_CHECK_LIB(avcodec,avcodec_find_decoder,,echo "Must have libavcodec";exit) + + if test x"$STATIC_LIBS" != x"no"; then + LIBS="${LIBS} ${STATIC_LIBS}/libavcodec.a ${STATIC_LIBS}/libavformat.a ${STATIC_LIBS}/libavutil.a" + else + LIBS="${LIBS} -lavcodec -lavformat -lavutil" + fi +fi + if test x$use_musepack = xtrue; then AC_PATH_PROG(TAGLIB_CONFIG, taglib-config, no) AC_CHECK_HEADERS(taglib/tag_c.h,, [ diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 283acc19..bab27e79 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -1,12 +1,19 @@ # $Id: $ # rspdir = ${pkgdatadir}/plugins +ssc_ffmpegdir = ${pkgdatadir}/plugins rsp_LTLIBRARIES=rsp.la rsp_la_LDFLAGS=-module -avoid-version rsp_la_SOURCES = compat.c rsp.c xml-rpc.c -EXTRA_DIST = compat.h rsp.h mtd-plugins.h xml-rpc.h +if COND_FFMPEG +ssc_ffmpeg_LTLIBRARIES=ssc-ffmpeg.la +ssc_ffmpeg_la_LDFLAGS=-module -avoid-version +ssc_ffmpeg_la_SOURCES=ssc-ffmpeg.c +endif + +EXTRA_DIST = compat.h rsp.h mtd-plugins.h xml-rpc.h ssc-ffmpeg.c AM_CFLAGS = -I.. diff --git a/src/plugins/ssc-ffmpeg.c b/src/plugins/ssc-ffmpeg.c new file mode 100644 index 00000000..51f324b8 --- /dev/null +++ b/src/plugins/ssc-ffmpeg.c @@ -0,0 +1,479 @@ +/* + * $Id: $ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "ff-plugins.h" + +#ifndef TRUE +# define TRUE 1 +# define FALSE 0 +#endif + +#define BUFFER_SIZE (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3)/2 + +typedef struct tag_ssc_handle { + AVCodec *pCodec; + AVCodecContext *pCodecCtx; + AVFormatContext *pFmtCtx; + AVFrame *pFrame; + AVPacket packet; + AVInputFormat *pFormat; + + uint8_t *packet_data; + int packet_size; + int audio_stream; + + char buffer[BUFFER_SIZE]; + + char *buf_remainder; + int buf_remainder_len; + int first_frame; + + int total_decoded; + int total_written; + + int errno; + int swab; + + char *error; + + int raw; + FILE *fin; + char file_buffer[256]; + char *file_buffer_ptr; + int file_bytes_read; + + char wav_header[44]; + int wav_offset; +} SSCHANDLE; + +#define SSC_FFMPEG_E_SUCCESS 0 +#define SSC_FFMPEG_E_BADCODEC 1 +#define SSC_FFMPEG_E_CODECOPEN 2 +#define SSC_FFMPEG_E_FILEOPEN 3 +#define SSC_FFMPEG_E_NOSTREAM 4 +#define SSC_FFMPEG_E_NOAUDIO 5 +#define SSC_FFMPEG_E_BADFORMAT 6 + + +/* Forwards */ +void *ssc_ffmpeg_init(void); +void ssc_ffmpeg_deinit(void *pv); +int ssc_ffmpeg_open(void *pv, char *file, char *codec); +int ssc_ffmpeg_close(void *pv); +int ssc_ffmpeg_read(void *pv, char *buffer, int len); + +PLUGIN_INFO *plugin_info(void); + +#define infn ((PLUGIN_INPUT_FN *)(_pi.pi)) + +/* Globals */ +PLUGIN_TRANSCODE_FN _ptfn = { + ssc_ffmpeg_init, + ssc_ffmpeg_deinit, + ssc_ffmpeg_open, + ssc_ffmpeg_close, + ssc_ffmpeg_read +}; + +PLUGIN_INFO _pi = { + PLUGIN_VERSION, /* version */ + PLUGIN_TRANSCODE, /* type */ + "ssc-ffmpeg/" VERSION, /* server */ + NULL, /* url */ + NULL, /* output fns */ + NULL, /* event fns */ + &_ptfn, /* fns */ + NULL, /* functions exported by ff */ + NULL, /* rend info */ + "flac,alac,ogg,wma" /* codeclist */ +}; + +PLUGIN_INFO *plugin_info(void) { + av_register_all(); + + return &_pi; +} + +void *ssc_ffmpeg_init(void) { + SSCHANDLE *handle; + + handle=(SSCHANDLE *)malloc(sizeof(SSCHANDLE)); + if(handle) { + memset(handle,0,sizeof(SSCHANDLE)); + } + + return (void*)handle; +} + +void ssc_ffmpeg_deinit(void *vp) { + SSCHANDLE *handle = (SSCHANDLE *)vp; + ssc_ffmpeg_close(handle); + if(handle) { + free(handle); + } + + return; +} + +int ssc_ffmpeg_open(void *vp, char *file, char *codec) { + int i; + enum CodecID id=CODEC_ID_FLAC; + SSCHANDLE *handle = (SSCHANDLE*)vp; + + if(!handle) + return FALSE; + + handle->first_frame = 1; + handle->raw=0; + + infn->log(E_DBG,"opening %s\n",file); + + if(strcasecmp(codec,"flac") == 0) { + handle->raw=1; + id=CODEC_ID_FLAC; + } + + if(handle->raw) { + handle->pCodec = avcodec_find_decoder(id); + if(!handle->pCodec) { + handle->errno = SSC_FFMPEG_E_BADCODEC; + return FALSE; + } + + handle->pCodecCtx = avcodec_alloc_context(); + if(avcodec_open(handle->pCodecCtx,handle->pCodec) < 0) { + handle->errno = SSC_FFMPEG_E_CODECOPEN; + return FALSE; + } + + handle->fin = fopen(file,"rb"); + if(!handle->fin) { + handle->errno = SSC_FFMPEG_E_FILEOPEN; + return FALSE; + } + + return TRUE; + } + + if(av_open_input_file(&handle->pFmtCtx,file,handle->pFormat,0,NULL) < 0) { + handle->errno = SSC_FFMPEG_E_FILEOPEN; + return FALSE; + } + + /* find the streams */ + if(av_find_stream_info(handle->pFmtCtx) < 0) { + handle->errno = SSC_FFMPEG_E_NOSTREAM; + return FALSE; + } + + dump_format(handle->pFmtCtx,0,file,FALSE); + + handle->audio_stream = -1; + for(i=0; i < handle->pFmtCtx->nb_streams; i++) { + if(handle->pFmtCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO) { + handle->audio_stream = i; + break; + } + } + + if(handle->audio_stream == -1) { + handle->errno = SSC_FFMPEG_E_NOAUDIO; + return FALSE; + } + + handle->pCodecCtx = handle->pFmtCtx->streams[handle->audio_stream]->codec; + + handle->pCodec = avcodec_find_decoder(handle->pCodecCtx->codec_id); + if(!handle->pCodec) { + handle->errno = SSC_FFMPEG_E_BADCODEC; + return FALSE; + } + + if(handle->pCodec->capabilities & CODEC_CAP_TRUNCATED) + handle->pCodecCtx->flags |= CODEC_FLAG_TRUNCATED; + + if(avcodec_open(handle->pCodecCtx, handle->pCodec) < 0) { + handle->errno = SSC_FFMPEG_E_CODECOPEN; + return FALSE; + } + + handle->pFrame = avcodec_alloc_frame(); + + return TRUE; +} + +int ssc_ffmpeg_close(void *vp) { + SSCHANDLE *handle = (SSCHANDLE *)vp; + + if(!handle) + return TRUE; + + if(handle->fin) + fclose(handle->fin); + + if(handle->pFrame) + av_free(handle->pFrame); + + if(handle->pCodecCtx) + avcodec_close(handle->pCodecCtx); + + if(handle->pFmtCtx) + av_close_input_file(handle->pFmtCtx); + + memset(handle,0,sizeof(SSCHANDLE)); + return TRUE; +} + + +int _ssc_ffmpeg_read_frame(void *vp, char *buffer, int len) { + SSCHANDLE *handle = (SSCHANDLE *)vp; + int data_size; + int len1; + int out_size; + + if(handle->raw) { + while(1) { + if(!handle->file_bytes_read) { + /* need to grab a new chunk */ + handle->file_buffer_ptr = handle->file_buffer; + handle->file_bytes_read = fread(handle->file_buffer, + 1, sizeof(handle->file_buffer), + handle->fin); + handle->file_buffer_ptr = handle->file_buffer; + } + + if(!handle->file_bytes_read) + return 0; + + len1 = avcodec_decode_audio(handle->pCodecCtx,(short*)buffer, + &out_size, + (uint8_t*)handle->file_buffer_ptr, + handle->file_bytes_read); + + if(len1 < 0) /* FIXME: Decode Error */ + return 0; + + handle->file_bytes_read -= len1; + handle->file_buffer_ptr += len1; + + if(out_size > 0) { + return out_size; + } + } + } + + + if(handle->first_frame) { + handle->first_frame = 0; + handle->packet.data = NULL; + } + + while(1) { + while(handle->packet_size > 0) { + len1=avcodec_decode_audio(handle->pCodecCtx, + (int16_t*)buffer, + &data_size, + handle->packet_data, + handle->packet_size); + + if(len1 < 0) { + /* skip frame */ + handle->packet_size=0; + break; + } + + handle->packet_data += len1; + handle->packet_size -= len1; + + if(data_size <= 0) + continue; + + handle->total_decoded += data_size; + return data_size; + } + + do { + if(handle->packet.data) + av_free_packet(&handle->packet); + + if(av_read_packet(handle->pFmtCtx, &handle->packet) < 0) + return -1; + } while(handle->packet.stream_index != handle->audio_stream); + + handle->packet_size = handle->packet.size; + handle->packet_data = handle->packet.data; + } +} + +void _ssc_ffmpeg_swab(char *buffer, int bytes_returned) { + int blocks = bytes_returned / 2; + int index; + char tmp; + + for(index = 0; index < blocks; index++) { + tmp = buffer[index*2]; + buffer[index*2] = buffer[index*2 + 1]; + buffer[index*2 + 1] = tmp; + } +} + +void _ssc_ffmpeg_le32(char *dst, int value) { + dst[0] = value & 0xFF; + dst[1] = (value >> 8) & 0xFF; + dst[2] = (value >> 16) & 0xFF; + dst[3] = (value >> 24) & 0xFF; +} + +void _ssc_ffmpeg_le16(char *dst, int value) { + dst[0] = value & 0xFF; + dst[1] = (value >> 8) & 0xFF; +} + +int ssc_ffmpeg_read(void *vp, char *buffer, int len) { + SSCHANDLE *handle = (SSCHANDLE *)vp; + int bytes_returned = 0; + int bytes_to_copy; + int size; + + int channels; + int sample_rate; + int bits_per_sample; + int byte_rate; + int duration = 180000; /* in ms -- 3 min */ + int data_len; + int block_align; + uint16_t test1 = 0xaabb; + char test2[2] = { 0xaa, 0xbb }; + + /* if we have not yet sent the header, let's do that first */ + if(handle->wav_offset != sizeof(handle->wav_header)) { + /* still have some to send */ + if(!handle->wav_offset) { + /* generate the wav header */ + if(handle->raw) { + channels = 2; + sample_rate = 44100; + bits_per_sample = 16; + } else { + channels = handle->pCodecCtx->channels; + sample_rate = handle->pCodecCtx->sample_rate; + switch(handle->pCodecCtx->sample_fmt) { + case SAMPLE_FMT_U8: + bits_per_sample = 8; + break; + case SAMPLE_FMT_S16: + bits_per_sample = 16; + break; + case SAMPLE_FMT_S24: + /* BROKEN */ + bits_per_sample = 24; + break; + case SAMPLE_FMT_S32: + /* BROKEN */ + bits_per_sample = 32; + break; + default: + bits_per_sample = 16; + break; + } + } + + handle->swab = (bits_per_sample == 16) && + (memcmp((void*)&test1,test2,2) == 0); + + data_len = (bits_per_sample * sample_rate * channels * (duration/1000)); + byte_rate = sample_rate * channels * bits_per_sample / 8; + block_align = channels * bits_per_sample / 8; + + infn->log(E_DBG,"Channels.......: %d\n",channels); + infn->log(E_DBG,"Sample rate....: %d\n",sample_rate); + infn->log(E_DBG,"Bits/Sample....: %d\n",bits_per_sample); + infn->log(E_DBG,"Swab...........: %d\n",handle->swab); + + memcpy(&handle->wav_header[0],"RIFF",4); + _ssc_ffmpeg_le32(&handle->wav_header[4],36 + data_len); + memcpy(&handle->wav_header[8],"WAVE",4); + memcpy(&handle->wav_header[12],"fmt ",4); + _ssc_ffmpeg_le32(&handle->wav_header[16],16); + _ssc_ffmpeg_le16(&handle->wav_header[20],1); + _ssc_ffmpeg_le16(&handle->wav_header[22],channels); + _ssc_ffmpeg_le32(&handle->wav_header[24],sample_rate); + _ssc_ffmpeg_le32(&handle->wav_header[28],byte_rate); + _ssc_ffmpeg_le16(&handle->wav_header[32],block_align); + _ssc_ffmpeg_le16(&handle->wav_header[34],bits_per_sample); + memcpy(&handle->wav_header[36],"data",4); + _ssc_ffmpeg_le32(&handle->wav_header[40],data_len); + } + + bytes_to_copy = sizeof(handle->wav_header) - handle->wav_offset; + if(len < bytes_to_copy) + bytes_to_copy = len; + + memcpy(buffer,&handle->wav_header[handle->wav_offset],bytes_to_copy); + handle->wav_offset += bytes_to_copy; + return bytes_to_copy; + } + + /* could test for good len here */ + + /* otherwise, start pumping out data */ + if(handle->buf_remainder_len) { + /* dump remainder into the buffer */ + bytes_to_copy = handle->buf_remainder_len; + if(handle->buf_remainder_len > len) { + bytes_to_copy = len; + } + + memcpy(buffer,handle->buf_remainder,bytes_to_copy); + bytes_returned = bytes_to_copy; + handle->buf_remainder_len -= bytes_to_copy; + if(handle->buf_remainder_len) { + handle->buf_remainder += bytes_returned; + } + } + + /* keep reading until we have filled the output buffer */ + while(bytes_returned < len) { + size = _ssc_ffmpeg_read_frame(handle,handle->buffer,BUFFER_SIZE); + if(size == 0) { + /* oops, we're done */ + if(handle->swab) + _ssc_ffmpeg_swab(buffer,bytes_returned); + return bytes_returned; + } + + if(size < 0) { + return 0; + } + + bytes_to_copy = len - bytes_returned; + if(size < bytes_to_copy) + bytes_to_copy = size; + + memcpy(buffer + bytes_returned, handle->buffer, bytes_to_copy); + bytes_returned += bytes_to_copy; + + if(size > bytes_to_copy) { + handle->buf_remainder = handle->buffer + bytes_to_copy; + handle->buf_remainder_len = size - bytes_to_copy; + } + + } + + if(handle->swab) + _ssc_ffmpeg_swab(buffer,bytes_returned); + + return bytes_returned; +}