[FFmpeg-devel,1/5] ffserver: Implement refcounted segments.

Submitted by Stephan Holljes on May 10, 2018, 3:41 p.m.

Details

Message ID 20180510154126.30789-2-klaxa1337@googlemail.com
State New
Headers show

Commit Message

Stephan Holljes May 10, 2018, 3:41 p.m.
Signed-off-by: Stephan Holljes <klaxa1337@googlemail.com>
---
 segment.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 segment.h | 114 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 281 insertions(+)
 create mode 100644 segment.c
 create mode 100644 segment.h

Patch hide | download patch | download mbox

diff --git a/segment.c b/segment.c
new file mode 100644
index 0000000..afb6d80
--- /dev/null
+++ b/segment.c
@@ -0,0 +1,167 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include "segment.h"
+#include <pthread.h>
+
+#include <libavutil/opt.h>
+#include <libavutil/log.h>
+
+
+void segment_save(struct Segment *seg, const char *filename)
+{
+    AVFormatContext *ofmt_ctx = NULL;
+    int ret;
+    
+    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);
+    if (!ofmt_ctx) {
+        av_log(NULL, AV_LOG_ERROR, "Could not allocate output to save Segment %d.\n", seg->id);
+        return;
+    }
+
+    if ((ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE)) < 0) {
+        av_log(ofmt_ctx, AV_LOG_ERROR,
+                "Could not open output io context to save Segment %d: %s.\n", seg->id, av_err2str(ret));
+        return;
+    }
+    
+    avio_write(ofmt_ctx->pb, seg->buf, seg->size);
+    avio_flush(ofmt_ctx->pb);
+    avio_close(ofmt_ctx->pb);
+    avformat_free_context(ofmt_ctx);
+}
+
+void segment_free(struct Segment *seg)
+{
+    av_log(NULL, AV_LOG_DEBUG, "Freeing segment\n");
+    avformat_free_context(seg->fmt_ctx);
+    av_free(seg->io_ctx->buffer);
+    av_free(seg->io_ctx);
+    av_free(seg->buf);
+    av_free(seg->ts);
+    av_free(seg);
+}
+
+void segment_ref(struct Segment *seg)
+{
+    pthread_mutex_lock(&seg->nb_read_lock);
+    seg->nb_read++;
+    av_log(NULL, AV_LOG_DEBUG, "%04d  ref Readers: %d\n", seg->id, seg->nb_read);
+    pthread_mutex_unlock(&seg->nb_read_lock);
+}
+
+void segment_unref(struct Segment *seg)
+{
+    pthread_mutex_lock(&seg->nb_read_lock);
+    seg->nb_read--;
+    pthread_mutex_unlock(&seg->nb_read_lock);
+    av_log(NULL, AV_LOG_DEBUG, "%04d unref Readers: %d\n", seg->id, seg->nb_read);
+    if (seg->nb_read == 0) {
+        segment_free(seg);
+    }
+}
+
+int segment_write(void *opaque, unsigned char *buf, int buf_size)
+{
+    struct Segment *seg = (struct Segment*) opaque;
+    seg->size += buf_size;
+    seg->buf = (unsigned char*) av_realloc(seg->buf, seg->size);
+    memcpy(seg->buf + seg->size - buf_size, buf, buf_size);
+    return buf_size;
+}
+
+int segment_read(void *opaque, unsigned char *buf, int buf_size)
+{
+    struct SegmentReadInfo *info = (struct SegmentReadInfo*) opaque;
+    buf_size = buf_size < info->left ? buf_size : info->left;
+
+    /* copy internal buffer data to buf */
+    memcpy(buf, info->buf, buf_size);
+    info->buf  += buf_size;
+    info->left -= buf_size;
+    return buf_size ? buf_size : AVERROR_EOF;
+}
+
+
+void segment_close(struct Segment *seg)
+{
+    av_write_trailer(seg->fmt_ctx);
+}
+
+void segment_init(struct Segment **seg_p, AVFormatContext *fmt)
+{
+    int ret;
+    int i;
+    AVStream *in_stream, *out_stream;
+    struct Segment *seg = (struct Segment*) av_malloc(sizeof(struct Segment));
+
+    seg->ifmt = fmt->iformat;
+    seg->fmt_ctx = NULL;
+    seg->nb_read = 0;
+    seg->size = 0;
+    seg->ts = NULL;
+    seg->ts_len = 0;
+    seg->buf = NULL;
+    seg->avio_buffer = (unsigned char*) av_malloc(AV_BUFSIZE);
+    pthread_mutex_init(&seg->nb_read_lock, NULL);
+    seg->io_ctx = avio_alloc_context(seg->avio_buffer, AV_BUFSIZE, 1, seg, NULL, &segment_write, NULL);
+    seg->io_ctx->seekable = 0;
+    avformat_alloc_output_context2(&seg->fmt_ctx, NULL, "matroska", NULL);
+    if ((ret = av_opt_set_int(seg->fmt_ctx, "flush_packets", 1, AV_OPT_SEARCH_CHILDREN)) < 0) {
+        av_log(seg->fmt_ctx, AV_LOG_WARNING, "Could not set flush_packets!\n");
+    }
+
+    seg->fmt_ctx->flags |= AVFMT_FLAG_GENPTS;
+    seg->fmt_ctx->oformat->flags &= AVFMT_NOFILE;
+
+    av_log(seg->fmt_ctx, AV_LOG_DEBUG, "Initializing segment\n");
+
+    for (i = 0; i < fmt->nb_streams; i++) {
+        in_stream = fmt->streams[i];
+        out_stream = avformat_new_stream(seg->fmt_ctx, NULL);
+        if (!out_stream) {
+            av_log(seg->fmt_ctx, AV_LOG_WARNING, "Failed allocating output stream\n");
+            continue;
+        }
+        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
+        if (ret < 0) {
+            av_log(seg->fmt_ctx, AV_LOG_WARNING, "Failed to copy context from input to output stream codec context\n");
+            continue;
+        }
+        if (out_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+            if (in_stream->sample_aspect_ratio.num)
+                out_stream->sample_aspect_ratio = in_stream->sample_aspect_ratio;
+            out_stream->avg_frame_rate = in_stream->avg_frame_rate;
+            out_stream->r_frame_rate = in_stream->r_frame_rate;
+        }
+        av_dict_copy(&out_stream->metadata, in_stream->metadata, 0);
+    }
+    av_dict_copy(&seg->fmt_ctx->metadata, fmt->metadata, 0);
+    seg->fmt_ctx->pb = seg->io_ctx;
+    ret = avformat_write_header(seg->fmt_ctx, NULL);
+    avio_flush(seg->io_ctx);
+    if (ret < 0) {
+        segment_close(seg);
+        av_log(seg->fmt_ctx, AV_LOG_WARNING, "Error occured while writing header: %s\n", av_err2str(ret));
+    }
+
+    *seg_p = seg;
+    av_log(seg->fmt_ctx, AV_LOG_DEBUG, "Initialized segment.\n");
+    return;
+}
diff --git a/segment.h b/segment.h
new file mode 100644
index 0000000..90724dd
--- /dev/null
+++ b/segment.h
@@ -0,0 +1,114 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SEGMENT_H
+#define SEGMENT_H
+
+#include <libavformat/avformat.h>
+
+#define AV_BUFSIZE 4096
+
+struct SegmentReadInfo {
+    unsigned char *buf;
+    int left;
+};
+
+struct Segment {
+    unsigned char *buf;
+    AVIOContext *io_ctx;
+    AVFormatContext *fmt_ctx;
+    AVInputFormat *ifmt;
+    size_t size;
+    int64_t *ts;
+    int ts_len;
+    int nb_read;
+    unsigned char *avio_buffer;
+    int id;
+    pthread_mutex_t nb_read_lock;
+};
+
+/**
+ * Save segment to a file using filename.
+ * Note: Currently produces incorrect files.
+ *
+ * @param seg pointer to the Segment to save
+ * @param filename string to use to save the Segment
+ */
+void segment_save(struct Segment *seg, const char *filename);
+
+/**
+ * Free Segment. Automatically called when a Segment's refcount reaches zero.
+ *
+ * @param seg pointer to the Segment to free
+ */
+void segment_free(struct Segment *seg);
+
+/**
+ * Increase the reference counter for a Segment.
+ *
+ * @param seg pointer to the Segment to increase the refcounter for
+ */
+void segment_ref(struct Segment *seg);
+
+/**
+ * Decrease the reference counter for a Segment. Calls segment_free() if the refcounter reaches zero.
+ *
+ * @param seg pointer to the Segment to unref
+ */
+void segment_unref(struct Segment *seg);
+
+
+/**
+ * Write buf_size bytes from buf to a Segment opaque.
+ *
+ * @param opaque void pointer to the Segment to write to
+ * @param buf pointer to the data to write
+ * @param buf_size number of bytes to write
+ * @return number of bytes written. May be less than buf_size.
+ */
+int segment_write(void *opaque, unsigned char *buf, int buf_size);
+
+/**
+ * Read buf_size bytes from a Segment using a SegmentReadInfo struct and store them in buf.
+ * Using a SegmentReadInfo struct instead of the Segment directly is needed, because there
+ * are multiple readers for a single Segment and each has to keep its own reading state.
+ *
+ * @param opaque void pointer to the SegmentReadInfo struct to use for reading
+ * @param buf pointer to where to store the read data
+ * @param buf_size number of bytes to read
+ * @return number of bytes read. May be less than buf_size.
+ */
+int segment_read(void *opaque, unsigned char *buf, int buf_size);
+
+/**
+ * Write a Segment's trailer
+ *
+ * @param seg pointer to the Segment to finalize
+ */
+void segment_close(struct Segment *seg);
+
+/**
+ * Allocate and initialize a new segment given the AVFormatContext fmt
+ *
+ * @param seg pointer to a pointer to a Segment to be allocated and initialized
+ * @param fmt pointer to an AVFormatContext describing the format of the Segment
+ */
+void segment_init(struct Segment **seg, AVFormatContext *fmt);
+
+
+#endif // SEGMENT_H