aboutsummaryrefslogtreecommitdiffstats
path: root/src/inputPlugins/ffmpeg_plugin.c
diff options
context:
space:
mode:
authorViliam Mateicka <viliam.mateicka@gmail.com>2008-10-17 22:27:33 +0200
committerMax Kellermann <max@duempel.org>2008-10-17 22:27:33 +0200
commit11ad9971415c18dfb8f2042b65da31d0102b8c91 (patch)
tree96cf617ba830f1c4037f10bd6bdf1eb8135f405f /src/inputPlugins/ffmpeg_plugin.c
parent4ee8da2e694d29efef5f6311d3bf8504d474918f (diff)
downloadmpd-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]
Diffstat (limited to 'src/inputPlugins/ffmpeg_plugin.c')
-rw-r--r--src/inputPlugins/ffmpeg_plugin.c416
1 files changed, 416 insertions, 0 deletions
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
+};