gstreamer/ext/ffmpeg/gstffmpegprotocol.c

377 lines
8.7 KiB
C
Raw Normal View History

/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* <2006> Edward Hervey <bilboed@bilboed.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <errno.h>
#ifdef HAVE_FFMPEG_UNINSTALLED
#include <avformat.h>
#else
#include <libavformat/avformat.h>
#endif
#include <gst/gst.h>
#include "gstffmpeg.h"
#include "gstffmpegpipe.h"
typedef struct _GstProtocolInfo GstProtocolInfo;
struct _GstProtocolInfo
{
GstPad *pad;
guint64 offset;
gboolean eos;
gint set_streamheader;
};
static int
gst_ffmpegdata_open (URLContext * h, const char *filename, int flags)
{
GstProtocolInfo *info;
GstPad *pad;
GST_LOG ("Opening %s", filename);
info = g_new0 (GstProtocolInfo, 1);
info->set_streamheader = flags & GST_FFMPEG_URL_STREAMHEADER;
flags &= ~GST_FFMPEG_URL_STREAMHEADER;
h->flags &= ~GST_FFMPEG_URL_STREAMHEADER;
/* we don't support R/W together */
if (flags != URL_RDONLY && flags != URL_WRONLY) {
GST_WARNING ("Only read-only or write-only are supported");
return -EINVAL;
}
if (sscanf (&filename[12], "%p", &pad) != 1) {
GST_WARNING ("could not decode pad from %s", filename);
return -EIO;
}
/* make sure we're a pad and that we're of the right type */
g_return_val_if_fail (GST_IS_PAD (pad), -EINVAL);
switch (flags) {
case URL_RDONLY:
g_return_val_if_fail (GST_PAD_IS_SINK (pad), -EINVAL);
break;
case URL_WRONLY:
g_return_val_if_fail (GST_PAD_IS_SRC (pad), -EINVAL);
break;
}
info->eos = FALSE;
HACKING: Add some basic documentation on how our wrapping works. Original commit message from CVS: * HACKING: Add some basic documentation on how our wrapping works. * TODO: Add a list of things that could be worked on or that need doing. * configure.ac: Update snapshot. * ext/ffmpeg/Makefile.am: Changne .la links. See below (autotools patch). * ext/ffmpeg/gstffmpeg.c: (plugin_init): Enable demuxers. See below (gstffmpegdemux.c). * ext/ffmpeg/gstffmpegcodecmap.c: (gst_ffmpeg_formatid_to_caps): Realmedia caused a crash - fix that. * ext/ffmpeg/gstffmpegdemux.c: (gst_ffmpegdemux_averror), (gst_ffmpegdemux_base_init), (gst_ffmpegdemux_init), (gst_ffmpegdemux_close), (gst_ffmpegdemux_dispose), (gst_ffmpegdemux_stream_from_pad), (gst_ffmpegdemux_src_event_mask), (gst_ffmpegdemux_src_event), (gst_ffmpegdemux_src_format_list), (gst_ffmpegdemux_src_query_list), (gst_ffmpegdemux_src_query), (gst_ffmpegdemux_src_convert), (gst_ffmpegdemux_add), (gst_ffmpegdemux_open), (gst_ffmpegdemux_loop), (gst_ffmpegdemux_change_state), (gst_ffmpegdemux_register): Right. OK, so I fixed up the demuxing and have it basically-working, and the best way to get some more people to test it is to actually enable it. I'm not sure if we want this for 0.8.0, but we can at least give it a try. I've tested avi, matroska and mpeg, all appear to work. The cool thing is that this gives us instant support for several exotic formats that we'd never care about ourselves. Again, this needs more testing for it to still be enabled in 0.8.0, but I want to give it a try... * ext/ffmpeg/gstffmpegmux.c: (gst_ffmpegmux_base_init), (gst_ffmpegmux_init), (gst_ffmpegmux_request_new_pad), (gst_ffmpegmux_connect), (gst_ffmpegmux_loop), (gst_ffmpegmux_register): Add some fixups that I use locally. Make it work in the case of MPEG encoding, but the muxer is still not in shape to be enabled. * ext/ffmpeg/gstffmpegprotocol.c: (gst_ffmpegdata_open), (gst_ffmpegdata_read), (gst_ffmpegdata_write), (gst_ffmpegdata_seek), (gst_ffmpegdata_close): Some small fixups that crept into it while it was disabled for the last few years. Basically works. * gst-libs/ext/ffmpeg/Makefile.am: Instead of having our local-autotoolized version, I patch the ffmpeg source to be fully autotoolized. That means a simple SUBDIRS here is now enough. * gst-libs/ext/ffmpeg/Tag: Version update. * gst-libs/ext/ffmpeg/patch/autotools.diff: Autotoolize ffmpeg. Needs to be sent to ffmpeg-devel@... * gst-libs/ext/ffmpeg/patch/disableinstalllibs.diff: Don't install their libs. * gst-libs/ext/ffmpeg/patch/disablemmx.diff: Don't use MMX. It cannot ocmpile using PIC. * gst-libs/ext/ffmpeg/patch/disabletools.diff: Don't compile/install their tools, we don't use them. * gst-libs/ext/ffmpeg/patch/functions.diff: Prevent symbol conflicts. * gst-libs/ext/ffmpeg/patch/matroska.diff: Add a matroska demuxer. Needs to be sent to ffmpeg-devel@...
2004-03-01 04:59:17 +00:00
info->pad = pad;
info->offset = 0;
h->priv_data = (void *) info;
h->is_streamed = FALSE;
h->max_packet_size = 0;
return 0;
}
static int
gst_ffmpegdata_peek (URLContext * h, unsigned char *buf, int size)
{
GstProtocolInfo *info;
GstBuffer *inbuf = NULL;
GstFlowReturn ret;
int total = 0;
g_return_val_if_fail (h->flags == URL_RDONLY, AVERROR_IO);
info = (GstProtocolInfo *) h->priv_data;
GST_DEBUG ("Pulling %d bytes at position %lld", size, info->offset);
ret = gst_pad_pull_range (info->pad, info->offset, (guint) size, &inbuf);
switch (ret) {
case GST_FLOW_OK:
total = (gint) GST_BUFFER_SIZE (inbuf);
memcpy (buf, GST_BUFFER_DATA (inbuf), total);
gst_buffer_unref (inbuf);
break;
case GST_FLOW_UNEXPECTED:
total = 0;
break;
case GST_FLOW_WRONG_STATE:
total = -1;
break;
default:
case GST_FLOW_ERROR:
total = -2;
break;
}
GST_DEBUG ("Got %d (%s) return result %d", ret, gst_flow_get_name (ret),
total);
return total;
}
static int
gst_ffmpegdata_read (URLContext * h, unsigned char *buf, int size)
{
gint res;
GstProtocolInfo *info;
info = (GstProtocolInfo *) h->priv_data;
GST_DEBUG ("Reading %d bytes of data at position %lld", size, info->offset);
res = gst_ffmpegdata_peek (h, buf, size);
if (res >= 0)
info->offset += res;
GST_DEBUG ("Returning %d bytes", res);
return res;
}
static int
gst_ffmpegdata_write (URLContext * h, unsigned char *buf, int size)
{
GstProtocolInfo *info;
GstBuffer *outbuf;
GST_DEBUG ("Writing %d bytes", size);
info = (GstProtocolInfo *) h->priv_data;
g_return_val_if_fail (h->flags != URL_RDONLY, -EIO);
/* create buffer and push data further */
if (gst_pad_alloc_buffer_and_set_caps (info->pad,
info->offset, size, GST_PAD_CAPS (info->pad), &outbuf) != GST_FLOW_OK)
return 0;
memcpy (GST_BUFFER_DATA (outbuf), buf, size);
if (gst_pad_push (info->pad, outbuf) != GST_FLOW_OK)
return 0;
info->offset += size;
return size;
}
static int64_t
gst_ffmpegdata_seek (URLContext * h, int64_t pos, int whence)
{
GstProtocolInfo *info;
guint64 newpos = 0;
GST_DEBUG ("Seeking to %" G_GINT64_FORMAT ", whence=%d", pos, whence);
info = (GstProtocolInfo *) h->priv_data;
/* TODO : if we are push-based, we need to return sensible info */
switch (h->flags) {
case URL_RDONLY:
{
/* sinkpad */
switch (whence) {
case SEEK_SET:
newpos = (guint64) pos;
break;
case SEEK_CUR:
newpos = info->offset + pos;
break;
case SEEK_END:
case AVSEEK_SIZE:
/* ffmpeg wants to know the current end position in bytes ! */
{
GstFormat format = GST_FORMAT_BYTES;
gint64 duration;
GST_DEBUG ("Seek end");
if (gst_pad_is_linked (info->pad))
if (gst_pad_query_duration (GST_PAD_PEER (info->pad), &format,
&duration))
newpos = ((guint64) duration) + pos;
}
break;
default:
g_assert (0);
break;
}
/* FIXME : implement case for push-based behaviour */
if (whence != AVSEEK_SIZE)
info->offset = newpos;
}
break;
case URL_WRONLY:
{
/* srcpad */
switch (whence) {
case SEEK_SET:
info->offset = (guint64) pos;
gst_pad_push_event (info->pad, gst_event_new_new_segment
(TRUE, 1.0, GST_FORMAT_BYTES, info->offset,
GST_CLOCK_TIME_NONE, info->offset));
break;
case SEEK_CUR:
info->offset += pos;
gst_pad_push_event (info->pad, gst_event_new_new_segment
(TRUE, 1.0, GST_FORMAT_BYTES, info->offset,
GST_CLOCK_TIME_NONE, info->offset));
break;
default:
break;
}
newpos = info->offset;
}
break;
default:
g_assert (0);
break;
}
GST_DEBUG ("Now at offset %lld (returning %lld)", info->offset, newpos);
return newpos;
}
static int
gst_ffmpegdata_close (URLContext * h)
{
GstProtocolInfo *info;
info = (GstProtocolInfo *) h->priv_data;
if (info == NULL)
return 0;
GST_LOG ("Closing file");
switch (h->flags) {
case URL_WRONLY:
{
/* send EOS - that closes down the stream */
gst_pad_push_event (info->pad, gst_event_new_eos ());
break;
}
default:
break;
}
/* clean up data */
g_free (info);
h->priv_data = NULL;
return 0;
}
URLProtocol gstreamer_protocol = {
/*.name = */ "gstreamer",
/*.url_open = */ gst_ffmpegdata_open,
/*.url_read = */ gst_ffmpegdata_read,
/*.url_write = */ gst_ffmpegdata_write,
/*.url_seek = */ gst_ffmpegdata_seek,
/*.url_close = */ gst_ffmpegdata_close,
};
/* specialized protocol for cross-thread pushing,
* based on ffmpeg's pipe protocol */
static int
gst_ffmpeg_pipe_open (URLContext * h, const char *filename, int flags)
{
GstFFMpegPipe *ffpipe;
GST_LOG ("Opening %s", filename);
/* we don't support W together */
if (flags != URL_RDONLY) {
GST_WARNING ("Only read-only is supported");
return -EINVAL;
}
if (sscanf (&filename[10], "%p", &ffpipe) != 1) {
GST_WARNING ("could not decode pipe info from %s", filename);
return -EIO;
}
/* sanity check */
g_return_val_if_fail (GST_IS_ADAPTER (ffpipe->adapter), -EINVAL);
h->priv_data = (void *) ffpipe;
h->is_streamed = TRUE;
h->max_packet_size = 0;
return 0;
}
static int
gst_ffmpeg_pipe_read (URLContext * h, unsigned char *buf, int size)
{
GstFFMpegPipe *ffpipe;
const guint8 *data;
guint available;
ffpipe = (GstFFMpegPipe *) h->priv_data;
GST_LOG ("requested size %d", size);
GST_FFMPEG_PIPE_MUTEX_LOCK (ffpipe);
while ((available = gst_adapter_available (ffpipe->adapter)) < size
&& !ffpipe->eos) {
ffpipe->needed = size;
GST_FFMPEG_PIPE_SIGNAL (ffpipe);
GST_FFMPEG_PIPE_WAIT (ffpipe);
}
size = MIN (available, size);
if (size) {
data = gst_adapter_peek (ffpipe->adapter, size);
memcpy (buf, data, size);
gst_adapter_flush (ffpipe->adapter, size);
ffpipe->needed = 0;
}
GST_FFMPEG_PIPE_MUTEX_UNLOCK (ffpipe);
return size;
}
static int
gst_ffmpeg_pipe_close (URLContext * h)
{
GST_LOG ("Closing pipe");
h->priv_data = NULL;
return 0;
}
URLProtocol gstpipe_protocol = {
"gstpipe",
gst_ffmpeg_pipe_open,
gst_ffmpeg_pipe_read,
NULL,
NULL,
gst_ffmpeg_pipe_close,
};