| /* 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., 51 Franklin St, Fifth Floor, | |
| * Boston, MA 02110-1301, USA. | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include "config.h" | |
| #endif | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <libavformat/avformat.h> | |
| #include <gst/gst.h> | |
| #include "gstav.h" | |
| #include "gstavprotocol.h" | |
| typedef struct _GstProtocolInfo GstProtocolInfo; | |
| struct _GstProtocolInfo | |
| { | |
| GstPad *pad; | |
| guint64 offset; | |
| gboolean eos; | |
| gint set_streamheader; | |
| }; | |
| static int | |
| gst_ffmpegdata_peek (void *priv_data, unsigned char *buf, int size) | |
| { | |
| GstProtocolInfo *info; | |
| GstBuffer *inbuf = NULL; | |
| GstFlowReturn ret; | |
| int total = 0; | |
| info = (GstProtocolInfo *) priv_data; | |
| GST_DEBUG ("Pulling %d bytes at position %" G_GUINT64_FORMAT, 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_get_size (inbuf); | |
| gst_buffer_extract (inbuf, 0, buf, total); | |
| gst_buffer_unref (inbuf); | |
| break; | |
| case GST_FLOW_EOS: | |
| total = 0; | |
| break; | |
| case GST_FLOW_FLUSHING: | |
| 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 (void *priv_data, unsigned char *buf, int size) | |
| { | |
| gint res; | |
| GstProtocolInfo *info; | |
| info = (GstProtocolInfo *) priv_data; | |
| GST_DEBUG ("Reading %d bytes of data at position %" G_GUINT64_FORMAT, size, | |
| info->offset); | |
| res = gst_ffmpegdata_peek (priv_data, buf, size); | |
| if (res >= 0) | |
| info->offset += res; | |
| GST_DEBUG ("Returning %d bytes", res); | |
| return res == 0 ? AVERROR_EOF : res; | |
| } | |
| static int | |
| #if LIBAVUTIL_VERSION_MAJOR >= 59 | |
| gst_ffmpegdata_write (void *priv_data, const uint8_t * buf, int size) | |
| #else | |
| gst_ffmpegdata_write (void *priv_data, uint8_t * buf, int size) | |
| #endif | |
| { | |
| GstProtocolInfo *info; | |
| GstBuffer *outbuf; | |
| GST_DEBUG ("Writing %d bytes", size); | |
| info = (GstProtocolInfo *) priv_data; | |
| /* create buffer and push data further */ | |
| outbuf = gst_buffer_new_and_alloc (size); | |
| gst_buffer_fill (outbuf, 0, 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 (void *priv_data, int64_t pos, int whence) | |
| { | |
| GstProtocolInfo *info; | |
| guint64 newpos = 0, oldpos; | |
| GST_DEBUG ("Seeking to %" G_GINT64_FORMAT ", whence=%d", | |
| (gint64) pos, whence); | |
| info = (GstProtocolInfo *) priv_data; | |
| /* TODO : if we are push-based, we need to return sensible info */ | |
| if (GST_PAD_IS_SINK (info->pad)) { | |
| /* 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 ! */ | |
| { | |
| gint64 duration; | |
| GST_DEBUG ("Seek end"); | |
| if (gst_pad_is_linked (info->pad)) | |
| if (gst_pad_query_duration (GST_PAD_PEER (info->pad), | |
| GST_FORMAT_BYTES, &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; | |
| } else if (GST_PAD_IS_SRC (info->pad)) { | |
| GstSegment segment; | |
| oldpos = info->offset; | |
| /* srcpad */ | |
| switch (whence) { | |
| case SEEK_SET: | |
| { | |
| info->offset = (guint64) pos; | |
| break; | |
| } | |
| case SEEK_CUR: | |
| info->offset += pos; | |
| break; | |
| default: | |
| break; | |
| } | |
| newpos = info->offset; | |
| if (newpos != oldpos) { | |
| gst_segment_init (&segment, GST_FORMAT_BYTES); | |
| segment.start = newpos; | |
| segment.time = newpos; | |
| gst_pad_push_event (info->pad, gst_event_new_segment (&segment)); | |
| } | |
| } else { | |
| g_assert_not_reached (); | |
| } | |
| GST_DEBUG ("Now at offset %" G_GUINT64_FORMAT " (returning %" G_GUINT64_FORMAT | |
| ")", info->offset, newpos); | |
| return newpos; | |
| } | |
| int | |
| gst_ffmpegdata_close (AVIOContext * h) | |
| { | |
| GstProtocolInfo *info; | |
| if (h == NULL) | |
| return 0; | |
| info = (GstProtocolInfo *) h->opaque; | |
| if (info == NULL) | |
| return 0; | |
| GST_LOG ("Closing file"); | |
| if (GST_PAD_IS_SRC (info->pad)) { | |
| /* send EOS - that closes down the stream */ | |
| gst_pad_push_event (info->pad, gst_event_new_eos ()); | |
| } | |
| /* clean up data */ | |
| g_free (info); | |
| h->opaque = NULL; | |
| av_freep (&h->buffer); | |
| av_free (h); | |
| return 0; | |
| } | |
| int | |
| gst_ffmpegdata_open (GstPad * pad, int flags, AVIOContext ** context) | |
| { | |
| GstProtocolInfo *info; | |
| static const int buffer_size = 4096; | |
| unsigned char *buffer = NULL; | |
| info = g_new0 (GstProtocolInfo, 1); | |
| info->set_streamheader = flags & GST_FFMPEG_URL_STREAMHEADER; | |
| flags &= ~GST_FFMPEG_URL_STREAMHEADER; | |
| /* we don't support R/W together */ | |
| if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) { | |
| GST_WARNING ("Only read-only or write-only are supported"); | |
| g_free (info); | |
| return -EINVAL; | |
| } | |
| /* make sure we're a pad and that we're of the right type */ | |
| g_return_val_if_fail (GST_IS_PAD (pad), -EINVAL); | |
| if ((flags & AVIO_FLAG_READ)) | |
| g_return_val_if_fail (GST_PAD_IS_SINK (pad), -EINVAL); | |
| if ((flags & AVIO_FLAG_WRITE)) | |
| g_return_val_if_fail (GST_PAD_IS_SRC (pad), -EINVAL); | |
| info->eos = FALSE; | |
| info->pad = pad; | |
| info->offset = 0; | |
| buffer = av_malloc (buffer_size); | |
| if (buffer == NULL) { | |
| GST_WARNING ("Failed to allocate buffer"); | |
| g_free (info); | |
| return -ENOMEM; | |
| } | |
| *context = | |
| avio_alloc_context (buffer, buffer_size, flags, (void *) info, | |
| gst_ffmpegdata_read, gst_ffmpegdata_write, gst_ffmpegdata_seek); | |
| if (*context == NULL) { | |
| GST_WARNING ("Failed to allocate memory"); | |
| g_free (info); | |
| av_free (buffer); | |
| return -ENOMEM; | |
| } | |
| (*context)->seekable = AVIO_SEEKABLE_NORMAL; | |
| if (!(flags & AVIO_FLAG_WRITE)) { | |
| (*context)->buf_ptr = (*context)->buf_end; | |
| (*context)->write_flag = 0; | |
| } | |
| return 0; | |
| } | |
| /* specialized protocol for cross-thread pushing, | |
| * based on ffmpeg's pipe protocol */ | |
| static int | |
| gst_ffmpeg_pipe_read (void *priv_data, uint8_t * buf, int size) | |
| { | |
| GstFFMpegPipe *ffpipe; | |
| guint available; | |
| ffpipe = (GstFFMpegPipe *) priv_data; | |
| GST_LOG ("requested size %d", size); | |
| GST_FFMPEG_PIPE_MUTEX_LOCK (ffpipe); | |
| GST_LOG ("requested size %d", size); | |
| while ((available = gst_adapter_available (ffpipe->adapter)) < size | |
| && !ffpipe->eos) { | |
| GST_DEBUG ("Available:%d, requested:%d", available, size); | |
| ffpipe->needed = size; | |
| GST_FFMPEG_PIPE_SIGNAL (ffpipe); | |
| GST_FFMPEG_PIPE_WAIT (ffpipe); | |
| } | |
| size = MIN (available, size); | |
| if (size) { | |
| GST_LOG ("Getting %d bytes", size); | |
| gst_adapter_copy (ffpipe->adapter, buf, 0, size); | |
| gst_adapter_flush (ffpipe->adapter, size); | |
| GST_LOG ("%" G_GSIZE_FORMAT " bytes left in adapter", | |
| gst_adapter_available (ffpipe->adapter)); | |
| ffpipe->needed = 0; | |
| } | |
| GST_FFMPEG_PIPE_MUTEX_UNLOCK (ffpipe); | |
| return size > 0 ? size : AVERROR_EOF; | |
| } | |
| int | |
| gst_ffmpeg_pipe_close (AVIOContext * h) | |
| { | |
| GST_LOG ("Closing pipe"); | |
| GstProtocolInfo *info; | |
| if (h == NULL) | |
| return 0; | |
| info = (GstProtocolInfo *) h->opaque; | |
| if (info == NULL) | |
| return 0; | |
| g_free (info); | |
| h->opaque = NULL; | |
| av_freep (&h->buffer); | |
| av_free (h); | |
| return 0; | |
| } | |
| int | |
| gst_ffmpeg_pipe_open (GstFFMpegPipe * ffpipe, int flags, AVIOContext ** context) | |
| { | |
| static const int buffer_size = 4096; | |
| unsigned char *buffer = NULL; | |
| /* sanity check */ | |
| g_return_val_if_fail (GST_IS_ADAPTER (ffpipe->adapter), -EINVAL); | |
| buffer = av_malloc (buffer_size); | |
| if (buffer == NULL) { | |
| GST_WARNING ("Failed to allocate buffer"); | |
| return -ENOMEM; | |
| } | |
| *context = | |
| avio_alloc_context (buffer, buffer_size, 0, (void *) ffpipe, | |
| gst_ffmpeg_pipe_read, NULL, NULL); | |
| if (*context == NULL) { | |
| GST_WARNING ("Failed to allocate memory"); | |
| av_free (buffer); | |
| return -ENOMEM; | |
| } | |
| (*context)->seekable = 0; | |
| (*context)->buf_ptr = (*context)->buf_end; | |
| return 0; | |
| } |