From 11ad9971415c18dfb8f2042b65da31d0102b8c91 Mon Sep 17 00:00:00 2001 From: Viliam Mateicka Date: Fri, 17 Oct 2008 22:27:33 +0200 Subject: 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] --- src/inputPlugins/ffmpeg_plugin.c | 416 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 src/inputPlugins/ffmpeg_plugin.c (limited to 'src/inputPlugins/ffmpeg_plugin.c') 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 + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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; inb_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 +}; -- cgit v1.2.3