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

Submitted by Stephan Holljes on April 12, 2018, 1:35 p.m.

Details

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

Commit Message

Stephan Holljes April 12, 2018, 1:35 p.m.
---
 segment.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 segment.h | 104 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 247 insertions(+)
 create mode 100644 segment.c
 create mode 100644 segment.h

Comments

Michael Niedermayer April 13, 2018, 2:08 p.m.
On Thu, Apr 12, 2018 at 03:35:46PM +0200, Stephan Holljes wrote:
> ---
>  segment.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  segment.h | 104 +++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 247 insertions(+)
>  create mode 100644 segment.c
>  create mode 100644 segment.h
> 
> diff --git a/segment.c b/segment.c
> new file mode 100644
> index 0000000..0ef58a1
> --- /dev/null
> +++ b/segment.c
> @@ -0,0 +1,143 @@
> +#include <stdio.h>
> +#include "segment.h"
> +#include <pthread.h>
> +
> +#include <libavutil/opt.h>
> +#include <libavutil/log.h>

This and other files lack a license header



> +
> +
> +void save_segment(struct Segment *seg, const char *filename)
> +{
> +    FILE *out = fopen(filename, "w");
> +    fwrite(seg->buf, seg->size, 1, out);
> +    fclose(out);
> +}

probably should use avio from libavformat so as not to limit it to local files


> +
> +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);
> +    free(seg->buf);
> +    free(seg->ts);
> +    free(seg);

iam not sure mixing free and av_free is a good idea. its a recipe for disaster



> +}
> +
> +void segment_ref(struct Segment *seg)
> +{
> +    pthread_mutex_lock(&seg->nb_read_lock);
> +    seg->nb_read++;
> +    av_log(NULL, AV_LOG_DEBUG, "  ref Readers: %d\n", 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, "unref Readers: %d\n", seg->nb_read);
> +    if (seg->nb_read == 0) {
> +        segment_free(seg);
> +    }
> +}
> +
> +void segment_ts_append(struct Segment *seg, int64_t dts, int64_t pts)
> +{
> +    seg->ts = (int64_t*) realloc(seg->ts, sizeof(int64_t) * 2  * (seg->ts_len + 2));
> +    seg->ts[seg->ts_len] = dts;
> +    seg->ts[seg->ts_len + 1] = pts;
> +    seg->ts_len += 2;
> +    return;
> +}
> +
> +int segment_write(void *opaque, unsigned char *buf, int buf_size)
> +{
> +    struct Segment *seg = (struct Segment*) opaque;
> +    seg->size += buf_size;
> +    seg->buf = (char*) 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;
> +    AVCodecContext *codec_ctx;
> +    struct Segment *seg = (struct Segment*) 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(NULL, 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(NULL, AV_LOG_DEBUG, "Initializing segment\n");

av_log should not be used with a NULL context, as it will make it hard for
the user/developer to identify where a message came from when things become
more complex than a single thread, single client

thanks

[...]

Patch hide | download patch | download mbox

diff --git a/segment.c b/segment.c
new file mode 100644
index 0000000..0ef58a1
--- /dev/null
+++ b/segment.c
@@ -0,0 +1,143 @@ 
+#include <stdio.h>
+#include "segment.h"
+#include <pthread.h>
+
+#include <libavutil/opt.h>
+#include <libavutil/log.h>
+
+
+void save_segment(struct Segment *seg, const char *filename)
+{
+    FILE *out = fopen(filename, "w");
+    fwrite(seg->buf, seg->size, 1, out);
+    fclose(out);
+}
+
+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);
+    free(seg->buf);
+    free(seg->ts);
+    free(seg);
+}
+
+void segment_ref(struct Segment *seg)
+{
+    pthread_mutex_lock(&seg->nb_read_lock);
+    seg->nb_read++;
+    av_log(NULL, AV_LOG_DEBUG, "  ref Readers: %d\n", 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, "unref Readers: %d\n", seg->nb_read);
+    if (seg->nb_read == 0) {
+        segment_free(seg);
+    }
+}
+
+void segment_ts_append(struct Segment *seg, int64_t dts, int64_t pts)
+{
+    seg->ts = (int64_t*) realloc(seg->ts, sizeof(int64_t) * 2  * (seg->ts_len + 2));
+    seg->ts[seg->ts_len] = dts;
+    seg->ts[seg->ts_len + 1] = pts;
+    seg->ts_len += 2;
+    return;
+}
+
+int segment_write(void *opaque, unsigned char *buf, int buf_size)
+{
+    struct Segment *seg = (struct Segment*) opaque;
+    seg->size += buf_size;
+    seg->buf = (char*) 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;
+    AVCodecContext *codec_ctx;
+    struct Segment *seg = (struct Segment*) 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(NULL, 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(NULL, AV_LOG_DEBUG, "Initializing segment\n");
+
+    for (i = 0; i < fmt->nb_streams; i++) {
+        in_stream = fmt->streams[i];
+        codec_ctx = avcodec_alloc_context3(NULL);
+        avcodec_parameters_to_context(codec_ctx, in_stream->codecpar);
+        out_stream = avformat_new_stream(seg->fmt_ctx, codec_ctx->codec);
+        avcodec_free_context(&codec_ctx);
+        if (!out_stream) {
+            av_log(NULL, AV_LOG_WARNING, "Failed allocating output stream\n");
+            continue;
+        }
+        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
+        if (ret < 0) {
+            av_log(NULL, AV_LOG_WARNING, "Failed to copy context from input to output stream codec context\n");
+            continue;
+        }
+        av_dict_copy(&out_stream->metadata, in_stream->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(NULL, AV_LOG_WARNING, "Error occured while writing header: %s\n", av_err2str(ret));
+    }
+
+
+    av_log(NULL, AV_LOG_DEBUG, "Initialized segment.\n");
+
+    *seg_p = seg;
+
+    return;
+}
diff --git a/segment.h b/segment.h
new file mode 100644
index 0000000..fecbc66
--- /dev/null
+++ b/segment.h
@@ -0,0 +1,104 @@ 
+#ifndef SEGMENT_H
+#define SEGMENT_H
+
+#include <libavformat/avformat.h>
+
+#define AV_BUFSIZE 4096
+
+struct SegmentReadInfo {
+    char *buf;
+    int left;
+};
+
+struct Segment {
+    char *buf;
+    AVIOContext *io_ctx;
+    AVFormatContext *fmt_ctx;
+    AVInputFormat *ifmt;
+    size_t size;
+    int64_t *ts;
+    size_t ts_len;
+    //FILE *stream;
+    int nb_read;
+    unsigned char *avio_buffer;
+    int id;
+    pthread_mutex_t nb_read_lock;
+};
+
+/**
+ * Save segment to a file using filename
+ *
+ * @param seg pointer to the Segment to save
+ * @param filename string to use to save the Segment
+ */
+void save_segment(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);
+
+/**
+ * Append a dts and pts pair to a Segment. This should be called after each frame is added.
+ *
+ * @param seg pointer to the Segment to add timestamps to
+ * @param dts dts for the last added frame
+ * @param pts pts for the last added frame
+ */
+void segment_ts_append(struct Segment *seg, int64_t dts, int64_t pts);
+
+/**
+ * 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