API changes, most recent first:
-2016-xx-xx - xxxxxxx - lavf 57.3.0 - avformat.h
++2016-xx-xx - xxxxxxx - lavf 57.25.0 - avformat.h
+ Add AVFormatContext.opaque, io_open and io_close, allowing custom IO
- for muxers and demuxers that open additional files.
+
-2015-xx-xx - xxxxxxx - lavc 57.12.0 - avcodec.h
+2016-02-01 - xxxxxxx - lavf 57.24.100
+ Add protocol_whitelist to AVFormatContext, AVIOContext
+
+2016-01-31 - xxxxxxx - lavu 55.17.100
+ Add AV_FRAME_DATA_GOP_TIMECODE for exporting MPEG1/2 GOP timecodes.
+
+2016-01-01 - xxxxxxx - lavc 57.21.100 / 57.12.0 - avcodec.h
Add AVCodecDescriptor.profiles and avcodec_profile_name().
-2015-xx-xx - xxxxxxx - lavc 57.11.0 - avcodec.h dirac.h
+2015-12-28 - xxxxxxx - lavf 57.21.100 - avformat.h
+ Add automatic bitstream filtering; add av_apply_bitstream_filters()
+
+2015-12-22 - xxxxxxx - lavfi 6.21.101 - avfilter.h
+ Deprecate avfilter_link_set_closed().
+ Applications are not supposed to mess with links,
+ they should close the sinks.
+
+2015-12-17 - lavc 57.18.100 / 57.11.0 - avcodec.h dirac.h
xxxxxxx - Add av_packet_add_side_data().
xxxxxxx - Add AVCodecContext.coded_side_data.
xxxxxxx - Add AVCPBProperties API.
AVFormatInternal *internal;
/**
- * Arbitrary user data set by the caller.
+ * IO repositioned flag.
+ * This is set by avformat when the underlaying IO context read pointer
+ * is repositioned, for example when doing byte based seeking.
+ * Demuxers can use the flag to detect such changes.
+ */
+ int io_repositioned;
+
+ /**
+ * Forced video codec.
+ * This allows forcing a specific decoder, even when there are multiple with
+ * the same codec_id.
+ * Demuxing: Set by user via av_format_set_video_codec (NO direct access).
+ */
+ AVCodec *video_codec;
+
+ /**
+ * Forced audio codec.
+ * This allows forcing a specific decoder, even when there are multiple with
+ * the same codec_id.
+ * Demuxing: Set by user via av_format_set_audio_codec (NO direct access).
+ */
+ AVCodec *audio_codec;
+
+ /**
+ * Forced subtitle codec.
+ * This allows forcing a specific decoder, even when there are multiple with
+ * the same codec_id.
+ * Demuxing: Set by user via av_format_set_subtitle_codec (NO direct access).
+ */
+ AVCodec *subtitle_codec;
+
+ /**
+ * Forced data codec.
+ * This allows forcing a specific decoder, even when there are multiple with
+ * the same codec_id.
+ * Demuxing: Set by user via av_format_set_data_codec (NO direct access).
+ */
+ AVCodec *data_codec;
+
+ /**
+ * Number of bytes to be written as padding in a metadata header.
+ * Demuxing: Unused.
+ * Muxing: Set by user via av_format_set_metadata_header_padding.
+ */
+ int metadata_header_padding;
+
+ /**
+ * User data.
+ * This is a place for some private data of the user.
- * Mostly usable with control_message_cb or any future callbacks in device's context.
*/
void *opaque;
/**
+ * Callback used by devices to communicate with application.
+ */
+ av_format_control_message control_message_cb;
+
+ /**
+ * Output timestamp offset, in microseconds.
+ * Muxing: set by user via AVOptions (NO direct access)
+ */
+ int64_t output_ts_offset;
+
+ /**
+ * dump format separator.
+ * can be ", " or "\n " or anything else
+ * Code outside libavformat should access this field using AVOptions
+ * (NO direct access).
+ * - muxing: Set by user.
+ * - demuxing: Set by user.
+ */
+ uint8_t *dump_separator;
+
+ /**
+ * Forced Data codec_id.
+ * Demuxing: Set by user.
+ */
+ enum AVCodecID data_codec_id;
+
++#if FF_API_OLD_OPEN_CALLBACKS
+ /**
+ * Called to open further IO contexts when needed for demuxing.
+ *
+ * This can be set by the user application to perform security checks on
+ * the URLs before opening them.
+ * The function should behave like avio_open2(), AVFormatContext is provided
+ * as contextual information and to reach AVFormatContext.opaque.
+ *
+ * If NULL then some simple checks are used together with avio_open2().
+ *
+ * Must not be accessed directly from outside avformat.
+ * @See av_format_set_open_cb()
+ *
+ * Demuxing: Set by user.
++ *
++ * @deprecated Use io_open and io_close.
+ */
++ attribute_deprecated
+ int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);
++#endif
+
+ /**
+ * ',' separated list of allowed protocols.
+ * - encoding: unused
+ * - decoding: set by user through AVOptions (NO direct access)
+ */
+ char *protocol_whitelist;
++
++ /*
+ * A callback for opening new IO streams.
+ *
+ * Certain muxers or demuxers (e.g. for various playlist-based formats) need
+ * to open additional files during muxing or demuxing. This callback allows
+ * the caller to provide custom IO in such cases.
+ *
+ * @param s the format context
+ * @param pb on success, the newly opened IO context should be returned here
+ * @param url the url to open
+ * @param flags a combination of AVIO_FLAG_*
+ * @param options a dictionary of additional options, with the same
+ * semantics as in avio_open2()
+ * @return 0 on success, a negative AVERROR code on failure
+ *
+ * @note Certain muxers and demuxers do nesting, i.e. they open one or more
+ * additional internal format contexts. Thus the AVFormatContext pointer
+ * passed to this callback may be different from the one facing the caller.
+ * It will, however, have the same 'opaque' field.
+ */
+ int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
+ int flags, AVDictionary **options);
+
+ /**
+ * A callback for closing the streams opened with AVFormatContext.io_open().
+ */
+ void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
} AVFormatContext;
- AVOpenCallback av_format_get_open_cb(const AVFormatContext *s);
- void av_format_set_open_cb(AVFormatContext *s, AVOpenCallback callback);
+int av_format_get_probe_score(const AVFormatContext *s);
+AVCodec * av_format_get_video_codec(const AVFormatContext *s);
+void av_format_set_video_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_audio_codec(const AVFormatContext *s);
+void av_format_set_audio_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_subtitle_codec(const AVFormatContext *s);
+void av_format_set_subtitle_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_data_codec(const AVFormatContext *s);
+void av_format_set_data_codec(AVFormatContext *s, AVCodec *c);
+int av_format_get_metadata_header_padding(const AVFormatContext *s);
+void av_format_set_metadata_header_padding(AVFormatContext *s, int c);
+void * av_format_get_opaque(const AVFormatContext *s);
+void av_format_set_opaque(AVFormatContext *s, void *opaque);
+av_format_control_message av_format_get_control_message_cb(const AVFormatContext *s);
+void av_format_set_control_message_cb(AVFormatContext *s, av_format_control_message callback);
++#if FF_API_OLD_OPEN_CALLBACKS
++attribute_deprecated AVOpenCallback av_format_get_open_cb(const AVFormatContext *s);
++attribute_deprecated void av_format_set_open_cb(AVFormatContext *s, AVOpenCallback callback);
++#endif
+
+/**
+ * This function will cause global side data to be injected in the next packet
+ * of each stream as well as after any subsequent seek.
+ */
+void av_format_inject_global_side_data(AVFormatContext *s);
+
+/**
+ * Returns the method used to set ctx->duration.
+ *
+ * @return AVFMT_DURATION_FROM_PTS, AVFMT_DURATION_FROM_STREAM, or AVFMT_DURATION_FROM_BITRATE.
+ */
+enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx);
+
typedef struct AVPacketList {
AVPacket pkt;
struct AVPacketList *next;
avio_printf(out, "\t</Period>\n");
avio_printf(out, "</MPD>\n");
avio_flush(out);
- avio_close(out);
+ ff_format_io_close(s, &out);
- return ff_rename(temp_filename, s->filename);
+ return ff_rename(temp_filename, s->filename, s);
}
static int dash_write_header(AVFormatContext *s)
return;
for (i = 0; i < s->nb_streams; i++) {
OutputStream *os = &c->streams[i];
- avio_closep(&os->out);
+ if (os->out)
+ ff_format_io_close(s, &os->out);
if (os->ctx && os->ctx_inited)
av_write_trailer(os->ctx);
- if (os->ctx && os->ctx->pb)
- av_free(os->ctx->pb);
+ if (os->ctx)
+ av_freep(&os->ctx->pb);
if (os->ctx)
avformat_free_context(os->ctx);
- av_free(os->metadata);
+ av_freep(&os->metadata);
for (j = 0; j < os->nb_extra_packets; j++)
- av_free(os->extra_packets[j]);
+ av_freep(&os->extra_packets[j]);
for (j = 0; j < os->nb_fragments; j++)
- av_free(os->fragments[j]);
- av_free(os->fragments);
+ av_freep(&os->fragments[j]);
+ av_freep(&os->fragments);
}
av_freep(&c->streams);
}
}
avio_printf(out, "</manifest>\n");
avio_flush(out);
- avio_close(out);
+ ff_format_io_close(s, &out);
- return ff_rename(temp_filename, filename);
+ return ff_rename(temp_filename, filename, s);
}
static void update_size(AVIOContext *out, int64_t pos)
}
update_size(out, afrt_pos);
update_size(out, 0);
- avio_close(out);
+ ff_format_io_close(s, &out);
- return ff_rename(temp_filename, filename);
+ return ff_rename(temp_filename, filename, s);
}
static int init_file(AVFormatContext *s, OutputStream *os, int64_t start_ts)
};
typedef struct HLSContext {
- AVFormatContext *avfmt;
+ AVClass *class;
+ AVFormatContext *ctx;
int n_variants;
struct variant **variants;
+ int n_playlists;
+ struct playlist **playlists;
+ int n_renditions;
+ struct rendition **renditions;
+
int cur_seq_no;
- int end_of_segment;
+ int live_start_index;
int first_packet;
int64_t first_timestamp;
- int64_t seek_timestamp;
- int seek_flags;
+ int64_t cur_timestamp;
AVIOInterruptCB *interrupt_callback;
+ char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context
+ char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context
+ char *headers; ///< holds HTTP headers set as an AVOption to the HTTP protocol context
+ char *http_proxy; ///< holds the address of the HTTP proxy server
AVDictionary *avio_opts;
+ int strict_std_compliance;
} HLSContext;
static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
{
AVDictionary *tmp = NULL;
int ret;
+ const char *proto_name = avio_find_protocol_name(url);
+
+ if (!proto_name)
+ return AVERROR_INVALIDDATA;
+
+ // only http(s) & file are allowed
+ if (!av_strstart(proto_name, "http", NULL) && !av_strstart(proto_name, "file", NULL))
+ return AVERROR_INVALIDDATA;
+ if (!strncmp(proto_name, url, strlen(proto_name)) && url[strlen(proto_name)] == ':')
+ ;
+ else if (strcmp(proto_name, "file") || !strncmp(url, "file,", 5))
+ return AVERROR_INVALIDDATA;
av_dict_copy(&tmp, c->avio_opts, 0);
+ av_dict_copy(&tmp, opts, 0);
- ret = ffurl_open_whitelist(uc, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp, c->avfmt->protocol_whitelist);
- ret = ffurl_open(uc, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp);
++ ret = ffurl_open_whitelist(uc, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp, c->ctx->protocol_whitelist);
+ if( ret >= 0) {
+ // update cookies on http response with setcookies.
+ URLContext *u = *uc;
+ update_options(&c->cookies, "cookies", u->priv_data);
+ av_dict_set(&opts, "cookies", c->cookies, 0);
+ }
av_dict_free(&tmp);
uint8_t iv[16] = "";
int has_iv = 0;
char key[MAX_URL_SIZE] = "";
- char line[1024];
+ char line[MAX_URL_SIZE];
const char *ptr;
int close_in = 0;
+ int64_t seg_offset = 0;
+ int64_t seg_size = -1;
uint8_t *new_url = NULL;
+ struct variant_info variant_info;
+ char tmp_str[MAX_URL_SIZE];
+ struct segment *cur_init_section = NULL;
if (!in) {
- ret = ffio_open_whitelist(&in, url, AVIO_FLAG_READ,
- c->interrupt_callback, &opts, c->avfmt->protocol_whitelist);
+#if 1
+ AVDictionary *opts = NULL;
+ close_in = 1;
+ /* Some HLS servers don't like being sent the range header */
+ av_dict_set(&opts, "seekable", "0", 0);
+
+ // broker prior HTTP options that should be consistent across requests
+ av_dict_set(&opts, "user-agent", c->user_agent, 0);
+ av_dict_set(&opts, "cookies", c->cookies, 0);
+ av_dict_set(&opts, "headers", c->headers, 0);
+ av_dict_set(&opts, "http_proxy", c->http_proxy, 0);
+
++ ret = c->ctx->io_open(c->ctx, &in, url, AVIO_FLAG_READ, &opts);
+ av_dict_free(&opts);
+ if (ret < 0)
+ return ret;
+#else
ret = open_in(c, &in, url);
if (ret < 0)
return ret;
HLSContext *c = s->priv_data;
int ret = 0, i, j, stream_offset = 0;
- c->avfmt = s;
+ c->ctx = s;
c->interrupt_callback = &s->interrupt_callback;
+ c->strict_std_compliance = s->strict_std_compliance;
+
+ c->first_packet = 1;
+ c->first_timestamp = AV_NOPTS_VALUE;
+ c->cur_timestamp = AV_NOPTS_VALUE;
+
+ // if the URL context is good, read important options we must broker later
+ if (u && u->prot->priv_data_class) {
+ // get the previous user agent & set back to null if string size is zero
+ update_options(&c->user_agent, "user-agent", u->priv_data);
+
+ // get the previous cookies & set back to null if string size is zero
+ update_options(&c->cookies, "cookies", u->priv_data);
+
+ // get the previous headers & set back to null if string size is zero
+ update_options(&c->headers, "headers", u->priv_data);
+
+ // get the previous http proxt & set back to null if string size is zero
+ update_options(&c->http_proxy, "http_proxy", u->priv_data);
+ }
if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0)
goto fail;
int64_t sequence;
int64_t start_sequence;
AVOutputFormat *oformat;
+ AVOutputFormat *vtt_oformat;
+
AVFormatContext *avf;
+ AVFormatContext *vtt_avf;
+
float time; // Set by a private option.
- int size; // Set by a private option.
+ int max_nb_segments; // Set by a private option.
int wrap; // Set by a private option.
- int version; // Set by a private option.
- int allowcache;
+ uint32_t flags; // enum HLSFlags
+ char *segment_filename;
+
+ int use_localtime; ///< flag to expand filename with localtime
+ int allowcache;
int64_t recording_time;
int has_video;
- // The following timestamps are in AV_TIME_BASE units.
+ int has_subtitle;
int64_t start_pts;
int64_t end_pts;
- int64_t duration; // last segment duration computed so far.
+ double duration; // last segment duration computed so far, in seconds
+ int64_t start_pos; // last segment starting position
+ int64_t size; // last segment size
int nb_entries;
- ListEntry *list;
- ListEntry *end_list;
+ int discontinuity_set;
+
+ HLSSegment *segments;
+ HLSSegment *last_segment;
+ HLSSegment *old_segments;
+
char *basename;
+ char *vtt_basename;
+ char *vtt_m3u8_name;
char *baseurl;
+ char *format_options_str;
+ char *vtt_format_options_str;
+ char *subtitle_filename;
+ AVDictionary *format_options;
+
+ char *key_info_file;
+ char key_file[LINE_BUFFER_SIZE + 1];
+ char key_uri[LINE_BUFFER_SIZE + 1];
+ char key_string[KEYSIZE*2 + 1];
+ char iv_string[KEYSIZE*2 + 1];
+ AVDictionary *vtt_format_options;
+
+ char *method;
+
} HLSContext;
- if ((ret = ffio_open_whitelist(&pb, hls->key_info_file, AVIO_FLAG_READ,
- &s->interrupt_callback, NULL, s->protocol_whitelist)) < 0) {
+static int hls_delete_old_segments(HLSContext *hls) {
+
+ HLSSegment *segment, *previous_segment = NULL;
+ float playlist_duration = 0.0f;
+ int ret = 0, path_size, sub_path_size;
+ char *dirname = NULL, *p, *sub_path;
+ char *path = NULL;
+
+ segment = hls->segments;
+ while (segment) {
+ playlist_duration += segment->duration;
+ segment = segment->next;
+ }
+
+ segment = hls->old_segments;
+ while (segment) {
+ playlist_duration -= segment->duration;
+ previous_segment = segment;
+ segment = previous_segment->next;
+ if (playlist_duration <= -previous_segment->duration) {
+ previous_segment->next = NULL;
+ break;
+ }
+ }
+
+ if (segment) {
+ if (hls->segment_filename) {
+ dirname = av_strdup(hls->segment_filename);
+ } else {
+ dirname = av_strdup(hls->avf->filename);
+ }
+ if (!dirname) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ p = (char *)av_basename(dirname);
+ *p = '\0';
+ }
+
+ while (segment) {
+ av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n",
+ segment->filename);
+ path_size = strlen(dirname) + strlen(segment->filename) + 1;
+ path = av_malloc(path_size);
+ if (!path) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ av_strlcpy(path, dirname, path_size);
+ av_strlcat(path, segment->filename, path_size);
+ if (unlink(path) < 0) {
+ av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n",
+ path, strerror(errno));
+ }
+
+ if (segment->sub_filename[0] != '\0') {
+ sub_path_size = strlen(dirname) + strlen(segment->sub_filename) + 1;
+ sub_path = av_malloc(sub_path_size);
+ if (!sub_path) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ av_strlcpy(sub_path, dirname, sub_path_size);
+ av_strlcat(sub_path, segment->sub_filename, sub_path_size);
+ if (unlink(sub_path) < 0) {
+ av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n",
+ sub_path, strerror(errno));
+ }
+ av_free(sub_path);
+ }
+ av_freep(&path);
+ previous_segment = segment;
+ segment = previous_segment->next;
+ av_free(previous_segment);
+ }
+
+fail:
+ av_free(path);
+ av_free(dirname);
+
+ return ret;
+}
+
+static int hls_encryption_start(AVFormatContext *s)
+{
+ HLSContext *hls = s->priv_data;
+ int ret;
+ AVIOContext *pb;
+ uint8_t key[KEYSIZE];
+
- avio_close(pb);
++ if ((ret = s->io_open(s, &pb, hls->key_info_file, AVIO_FLAG_READ, NULL)) < 0) {
+ av_log(hls, AV_LOG_ERROR,
+ "error opening key info file %s\n", hls->key_info_file);
+ return ret;
+ }
+
+ ff_get_line(pb, hls->key_uri, sizeof(hls->key_uri));
+ hls->key_uri[strcspn(hls->key_uri, "\r\n")] = '\0';
+
+ ff_get_line(pb, hls->key_file, sizeof(hls->key_file));
+ hls->key_file[strcspn(hls->key_file, "\r\n")] = '\0';
+
+ ff_get_line(pb, hls->iv_string, sizeof(hls->iv_string));
+ hls->iv_string[strcspn(hls->iv_string, "\r\n")] = '\0';
+
- if ((ret = ffio_open_whitelist(&pb, hls->key_file, AVIO_FLAG_READ,
- &s->interrupt_callback, NULL, s->protocol_whitelist)) < 0) {
++ ff_format_io_close(s, &pb);
+
+ if (!*hls->key_uri) {
+ av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (!*hls->key_file) {
+ av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n");
+ return AVERROR(EINVAL);
+ }
+
- avio_close(pb);
++ if ((ret = s->io_open(s, &pb, hls->key_file, AVIO_FLAG_READ, NULL)) < 0) {
+ av_log(hls, AV_LOG_ERROR, "error opening key file %s\n", hls->key_file);
+ return ret;
+ }
+
+ ret = avio_read(pb, key, sizeof(key));
++ ff_format_io_close(s, &pb);
+ if (ret != sizeof(key)) {
+ av_log(hls, AV_LOG_ERROR, "error reading key file %s\n", hls->key_file);
+ if (ret >= 0 || ret == AVERROR_EOF)
+ ret = AVERROR(EINVAL);
+ return ret;
+ }
+ ff_data_to_hex(hls->key_string, key, sizeof(key), 0);
+
+ return 0;
+}
+
static int hls_mux_init(AVFormatContext *s)
{
HLSContext *hls = s->priv_data;
oc->oformat = hls->oformat;
oc->interrupt_callback = s->interrupt_callback;
+ oc->max_delay = s->max_delay;
+ oc->opaque = s->opaque;
+ oc->io_open = s->io_open;
+ oc->io_close = s->io_close;
+ av_dict_copy(&oc->metadata, s->metadata, 0);
+
+ if(hls->vtt_oformat) {
+ ret = avformat_alloc_output_context2(&hls->vtt_avf, hls->vtt_oformat, NULL, NULL);
+ if (ret < 0)
+ return ret;
+ vtt_oc = hls->vtt_avf;
+ vtt_oc->oformat = hls->vtt_oformat;
+ av_dict_copy(&vtt_oc->metadata, s->metadata, 0);
+ }
for (i = 0; i < s->nb_streams; i++) {
AVStream *st;
static int hls_window(AVFormatContext *s, int last)
{
HLSContext *hls = s->priv_data;
- ListEntry *en;
- int64_t target_duration = 0;
+ HLSSegment *en;
+ int target_duration = 0;
int ret = 0;
AVIOContext *out = NULL;
+ AVIOContext *sub_out = NULL;
char temp_filename[1024];
- int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->size);
-
- snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", s->filename);
+ int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries);
+ int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3;
+ const char *proto = avio_find_protocol_name(s->filename);
+ int use_rename = proto && !strcmp(proto, "file");
+ static unsigned warned_non_file;
+ char *key_uri = NULL;
+ char *iv_string = NULL;
+ AVDictionary *options = NULL;
+
+ if (!use_rename && !warned_non_file++)
+ av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporarly partial files\n");
+
+ set_http_options(&options, hls);
+ snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", s->filename);
- if ((ret = ffio_open_whitelist(&out, temp_filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, &options, s->protocol_whitelist)) < 0)
+ if ((ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL)) < 0)
goto fail;
- for (en = hls->list; en; en = en->next) {
+ for (en = hls->segments; en; en = en->next) {
if (target_duration < en->duration)
- target_duration = en->duration;
+ target_duration = ceil(en->duration);
}
+ hls->discontinuity_set = 0;
avio_printf(out, "#EXTM3U\n");
- avio_printf(out, "#EXT-X-VERSION:%d\n", hls->version);
+ avio_printf(out, "#EXT-X-VERSION:%d\n", version);
if (hls->allowcache == 0 || hls->allowcache == 1) {
avio_printf(out, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES");
}
av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n",
sequence);
-
- for (en = hls->list; en; en = en->next) {
- if (hls->version > 2)
- avio_printf(out, "#EXTINF:%f\n",
- (double)en->duration / AV_TIME_BASE);
+ if((hls->flags & HLS_DISCONT_START) && sequence==hls->start_sequence && hls->discontinuity_set==0 ){
+ avio_printf(out, "#EXT-X-DISCONTINUITY\n");
+ hls->discontinuity_set = 1;
+ }
+ for (en = hls->segments; en; en = en->next) {
+ if (hls->key_info_file && (!key_uri || strcmp(en->key_uri, key_uri) ||
+ av_strcasecmp(en->iv_string, iv_string))) {
+ avio_printf(out, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri);
+ if (*en->iv_string)
+ avio_printf(out, ",IV=0x%s", en->iv_string);
+ avio_printf(out, "\n");
+ key_uri = en->key_uri;
+ iv_string = en->iv_string;
+ }
+
+ if (hls->flags & HLS_ROUND_DURATIONS)
+ avio_printf(out, "#EXTINF:%ld,\n", lrint(en->duration));
else
- avio_printf(out, "#EXTINF:%"PRId64",\n",
- av_rescale(en->duration, 1, AV_TIME_BASE));
+ avio_printf(out, "#EXTINF:%f,\n", en->duration);
+ if (hls->flags & HLS_SINGLE_FILE)
+ avio_printf(out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
+ en->size, en->pos);
if (hls->baseurl)
avio_printf(out, "%s", hls->baseurl);
- avio_printf(out, "%s\n", en->name);
+ avio_printf(out, "%s\n", en->filename);
}
- if (last)
+ if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)
avio_printf(out, "#EXT-X-ENDLIST\n");
- if ((ret = ffio_open_whitelist(&sub_out, hls->vtt_m3u8_name, AVIO_FLAG_WRITE,
- &s->interrupt_callback, &options, s->protocol_whitelist)) < 0)
+ if( hls->vtt_m3u8_name ) {
++ if ((ret = s->io_open(s, &sub_out, hls->vtt_m3u8_name, AVIO_FLAG_WRITE, &options)) < 0)
+ goto fail;
+ avio_printf(sub_out, "#EXTM3U\n");
+ avio_printf(sub_out, "#EXT-X-VERSION:%d\n", version);
+ if (hls->allowcache == 0 || hls->allowcache == 1) {
+ avio_printf(sub_out, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES");
+ }
+ avio_printf(sub_out, "#EXT-X-TARGETDURATION:%d\n", target_duration);
+ avio_printf(sub_out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
+
+ av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n",
+ sequence);
+
+ for (en = hls->segments; en; en = en->next) {
+ avio_printf(sub_out, "#EXTINF:%f,\n", en->duration);
+ if (hls->flags & HLS_SINGLE_FILE)
+ avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
+ en->size, en->pos);
+ if (hls->baseurl)
+ avio_printf(sub_out, "%s", hls->baseurl);
+ avio_printf(sub_out, "%s\n", en->sub_filename);
+ }
+
+ if (last)
+ avio_printf(sub_out, "#EXT-X-ENDLIST\n");
+
+ }
+
fail:
- avio_closep(&out);
- avio_closep(&sub_out);
+ av_dict_free(&options);
- if (ret >= 0)
- ff_rename(temp_filename, s->filename);
+ ff_format_io_close(s, &out);
++ ff_format_io_close(s, &sub_out);
+ if (ret >= 0 && use_rename)
+ ff_rename(temp_filename, s->filename, s);
return ret;
}
{
HLSContext *c = s->priv_data;
AVFormatContext *oc = c->avf;
+ AVFormatContext *vtt_oc = c->vtt_avf;
+ AVDictionary *options = NULL;
+ char *filename, iv_string[KEYSIZE*2 + 1];
int err = 0;
- if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
- c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0)
- return AVERROR(EINVAL);
+ if (c->flags & HLS_SINGLE_FILE) {
+ av_strlcpy(oc->filename, c->basename,
+ sizeof(oc->filename));
+ if (c->vtt_basename)
+ av_strlcpy(vtt_oc->filename, c->vtt_basename,
+ sizeof(vtt_oc->filename));
+ } else {
+ if (c->use_localtime) {
+ time_t now0;
+ struct tm *tm, tmpbuf;
+ time(&now0);
+ tm = localtime_r(&now0, &tmpbuf);
+ if (!strftime(oc->filename, sizeof(oc->filename), c->basename, tm)) {
+ av_log(oc, AV_LOG_ERROR, "Could not get segment filename with use_localtime\n");
+ return AVERROR(EINVAL);
+ }
+ } else if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
+ c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) {
+ av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try use -use_localtime 1 with it\n", c->basename);
+ return AVERROR(EINVAL);
+ }
+ if( c->vtt_basename) {
+ if (av_get_frame_filename(vtt_oc->filename, sizeof(vtt_oc->filename),
+ c->vtt_basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) {
+ av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->vtt_basename);
+ return AVERROR(EINVAL);
+ }
+ }
+ }
c->number++;
- if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0)
- return err;
+ set_http_options(&options, c);
+
+ if (c->key_info_file) {
+ if ((err = hls_encryption_start(s)) < 0)
+ goto fail;
+ if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0))
+ < 0)
+ goto fail;
+ err = av_strlcpy(iv_string, c->iv_string, sizeof(iv_string));
+ if (!err)
+ snprintf(iv_string, sizeof(iv_string), "%032"PRIx64, c->sequence);
+ if ((err = av_dict_set(&options, "encryption_iv", iv_string, 0)) < 0)
+ goto fail;
+
+ filename = av_asprintf("crypto:%s", oc->filename);
+ if (!filename) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
- err = ffio_open_whitelist(&oc->pb, filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, &options, s->protocol_whitelist);
++ err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL);
+ av_free(filename);
+ av_dict_free(&options);
+ if (err < 0)
+ return err;
+ } else
- if ((err = ffio_open_whitelist(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, &options, s->protocol_whitelist)) < 0)
++ if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, &options)) < 0)
+ goto fail;
+ if (c->vtt_basename) {
+ set_http_options(&options, c);
- if ((err = ffio_open_whitelist(&vtt_oc->pb, vtt_oc->filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, &options, s->protocol_whitelist)) < 0)
++ if ((err = s->io_open(s, &vtt_oc->pb, vtt_oc->filename, AVIO_FLAG_WRITE, &options)) < 0)
+ goto fail;
+ }
+ av_dict_free(&options);
+
+ /* We only require one PAT/PMT per segment. */
+ if (oc->oformat->priv_class && oc->priv_data) {
+ char period[21];
+
+ snprintf(period, sizeof(period), "%d", (INT_MAX / 2) - 1);
- if (oc->oformat->priv_class && oc->priv_data)
av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
+ av_opt_set(oc->priv_data, "sdt_period", period, 0);
+ av_opt_set(oc->priv_data, "pat_period", period, 0);
+ }
+
+ if (c->vtt_basename) {
+ err = avformat_write_header(vtt_oc,NULL);
+ if (err < 0)
+ return err;
+ }
return 0;
+fail:
+ av_dict_free(&options);
+
+ return err;
}
static int hls_write_header(AVFormatContext *s)
if (hls->has_video) {
can_split = st->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
pkt->flags & AV_PKT_FLAG_KEY;
+ is_ref_pkt = st->codec->codec_type == AVMEDIA_TYPE_VIDEO;
}
if (pkt->pts == AV_NOPTS_VALUE)
- can_split = 0;
- else
- hls->duration = pts - hls->end_pts;
+ is_ref_pkt = can_split = 0;
+
+ if (is_ref_pkt)
+ hls->duration = (double)(pkt->pts - hls->end_pts)
+ * st->time_base.num / st->time_base.den;
+
+ if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
+ end_pts, AV_TIME_BASE_Q) >= 0) {
+ int64_t new_start_pos;
+ av_write_frame(oc, NULL); /* Flush any buffered data */
- if (can_split && pts - hls->start_pts >= end_pts) {
- ret = append_entry(hls, hls->duration);
- if (ret)
+ new_start_pos = avio_tell(hls->avf->pb);
+ hls->size = new_start_pos - hls->start_pos;
+ ret = hls_append_segment(hls, hls->duration, hls->start_pos, hls->size);
+ hls->start_pos = new_start_pos;
+ if (ret < 0)
return ret;
- hls->end_pts = pts;
+ hls->end_pts = pkt->pts;
hls->duration = 0;
- av_write_frame(oc, NULL); /* Flush any buffered data */
- ff_format_io_close(s, &oc->pb);
+ if (hls->flags & HLS_SINGLE_FILE) {
+ if (hls->avf->oformat->priv_class && hls->avf->priv_data)
+ av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0);
+ hls->number++;
+ } else {
- avio_closep(&oc->pb);
++ ff_format_io_close(s, &oc->pb);
+ if (hls->vtt_avf)
- avio_close(hls->vtt_avf->pb);
++ ff_format_io_close(s, &hls->vtt_avf->pb);
- ret = hls_start(s);
+ ret = hls_start(s);
+ }
- if (ret)
+ if (ret < 0)
return ret;
+ if( st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE )
+ oc = hls->vtt_avf;
+ else
oc = hls->avf;
if ((ret = hls_window(s, 0)) < 0)
{
HLSContext *hls = s->priv_data;
AVFormatContext *oc = hls->avf;
+ AVFormatContext *vtt_oc = hls->vtt_avf;
av_write_trailer(oc);
- ff_format_io_close(s, &oc->pb);
+ if (oc->pb) {
+ hls->size = avio_tell(hls->avf->pb) - hls->start_pos;
- avio_closep(&oc->pb);
++ ff_format_io_close(s, &oc->pb);
+ hls_append_segment(hls, hls->duration, hls->start_pos, hls->size);
+ }
+
+ if (vtt_oc) {
+ if (vtt_oc->pb)
+ av_write_trailer(vtt_oc);
+ hls->size = avio_tell(hls->vtt_avf->pb) - hls->start_pos;
- avio_closep(&vtt_oc->pb);
++ ff_format_io_close(s, &vtt_oc->pb);
+ }
+ av_freep(&hls->basename);
avformat_free_context(oc);
- av_free(hls->basename);
- append_entry(hls, hls->duration);
+
+ if (vtt_oc) {
+ av_freep(&hls->vtt_basename);
+ av_freep(&hls->vtt_m3u8_name);
+ avformat_free_context(vtt_oc);
+ }
+
+ hls->avf = NULL;
hls_window(s, 1);
- free_entries(hls);
+ hls_free_segments(hls->segments);
+ hls_free_segments(hls->old_segments);
return 0;
}
s->path,
s->img_number) < 0 && s->img_number > 1)
return AVERROR(EIO);
+ }
for (i = 0; i < 3; i++) {
- if (s1->io_open(s1, &f[i], filename, AVIO_FLAG_READ, NULL) < 0) {
+ if (s1->pb &&
+ !strcmp(filename_bytes, s->path) &&
+ !s->loop &&
+ !s->split_planes) {
+ f[i] = s1->pb;
- } else if (open_func(s1, &f[i], filename, AVIO_FLAG_READ,
- &s1->interrupt_callback, NULL) < 0) {
++ } else if (s1->io_open(s1, &f[i], filename, AVIO_FLAG_READ, NULL) < 0) {
if (i >= 1)
break;
av_log(s1, AV_LOG_ERROR, "Could not open file : %s\n",
for (i = 0; i < 3; i++) {
if (f[i]) {
ret[i] = avio_read(f[i], pkt->data + pkt->size, size[i]);
- if (!s->is_pipe)
+ if (s->loop && s->is_pipe && ret[i] == AVERROR_EOF) {
+ if (avio_seek(f[i], 0, SEEK_SET) >= 0) {
+ pkt->pos = 0;
+ ret[i] = avio_read(f[i], pkt->data + pkt->size, size[i]);
+ }
+ }
+ if (!s->is_pipe && f[i] != s1->pb)
- avio_closep(&f[i]);
+ ff_format_io_close(s1, &f[i]);
if (ret[i] > 0)
pkt->size += ret[i];
}
} else {
s->img_count++;
s->img_number++;
+ s->pts++;
return 0;
}
- avio_closep(&f[i]);
+
+fail:
+ if (!s->is_pipe) {
+ for (i = 0; i < 3; i++) {
+ if (f[i] != s1->pb)
++ ff_format_io_close(s1, &f[i]);
+ }
+ }
+ return res;
+}
+
+static int img_read_close(struct AVFormatContext* s1)
+{
+#if HAVE_GLOB
+ VideoDemuxData *s = s1->priv_data;
+ if (s->use_glob) {
+ globfree(&s->globstate);
+ }
+#endif
+ return 0;
+}
+
+static int img_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
+{
+ VideoDemuxData *s1 = s->priv_data;
+ AVStream *st = s->streams[0];
+
+ if (s1->ts_from_file) {
+ int index = av_index_search_timestamp(st, timestamp, flags);
+ if(index < 0)
+ return -1;
+ s1->img_number = st->index_entries[index].pos;
+ return 0;
+ }
+
+ if (timestamp < 0 || !s1->loop && timestamp > s1->img_last - s1->img_first)
+ return -1;
+ s1->img_number = timestamp%(s1->img_last - s1->img_first + 1) + s1->img_first;
+ s1->pts = timestamp;
+ return 0;
}
#define OFFSET(x) offsetof(VideoDemuxData, x)
} else if (av_get_frame_filename(filename, sizeof(filename), img->path, img->img_number) < 0 &&
img->img_number > 1) {
av_log(s, AV_LOG_ERROR,
- "Could not get frame filename number %d from pattern '%s'\n",
+ "Could not get frame filename number %d from pattern '%s' (either set updatefirst or use a pattern like %%03d within the filename pattern)\n",
img->img_number, img->path);
- return AVERROR(EIO);
+ return AVERROR(EINVAL);
}
- for (i = 0; i < 3; i++) {
- if (s->io_open(s, &pb[i], img->tmp, AVIO_FLAG_WRITE, NULL) < 0) {
- av_log(s, AV_LOG_ERROR, "Could not open file : %s\n", img->tmp);
+ for (i = 0; i < 4; i++) {
+ snprintf(img->tmp[i], sizeof(img->tmp[i]), "%s.tmp", filename);
+ av_strlcpy(img->target[i], filename, sizeof(img->target[i]));
- if (avio_open2(&pb[i], img->use_rename ? img->tmp[i] : filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL) < 0) {
++ if (s->io_open(s, &pb[i], img->use_rename ? img->tmp[i] : filename, AVIO_FLAG_WRITE, NULL) < 0) {
+ av_log(s, AV_LOG_ERROR, "Could not open file : %s\n", img->use_rename ? img->tmp[i] : filename);
return AVERROR(EIO);
}
pb[0] = s->pb;
}
- if (codec->codec_id == AV_CODEC_ID_RAWVIDEO) {
+ if (img->split_planes) {
int ysize = codec->width * codec->height;
- avio_write(pb[0], pkt->data, ysize);
- avio_write(pb[1], pkt->data + ysize, (pkt->size - ysize) / 2);
- avio_write(pb[2], pkt->data + ysize + (pkt->size - ysize) / 2, (pkt->size - ysize) / 2);
+ int usize = AV_CEIL_RSHIFT(codec->width, desc->log2_chroma_w) * AV_CEIL_RSHIFT(codec->height, desc->log2_chroma_h);
+ if (desc->comp[0].depth >= 9) {
+ ysize *= 2;
+ usize *= 2;
+ }
+ avio_write(pb[0], pkt->data , ysize);
+ avio_write(pb[1], pkt->data + ysize , usize);
+ avio_write(pb[2], pkt->data + ysize + usize, usize);
- avio_closep(&pb[1]);
- avio_closep(&pb[2]);
+ ff_format_io_close(s, &pb[1]);
+ ff_format_io_close(s, &pb[2]);
- } else {
- if (ff_guess_image2_codec(s->filename) == AV_CODEC_ID_JPEG2000) {
- AVStream *st = s->streams[0];
- if (st->codec->extradata_size > 8 &&
- AV_RL32(st->codec->extradata + 4) == MKTAG('j', 'p', '2', 'h')) {
- if (pkt->size < 8 ||
- AV_RL32(pkt->data + 4) != MKTAG('j', 'p', '2', 'c'))
- goto error;
- avio_wb32(pb[0], 12);
- ffio_wfourcc(pb[0], "jP ");
- avio_wb32(pb[0], 0x0D0A870A); // signature
- avio_wb32(pb[0], 20);
- ffio_wfourcc(pb[0], "ftyp");
- ffio_wfourcc(pb[0], "jp2 ");
- avio_wb32(pb[0], 0);
- ffio_wfourcc(pb[0], "jp2 ");
- avio_write(pb[0], st->codec->extradata, st->codec->extradata_size);
- } else if (pkt->size < 8 ||
- (!st->codec->extradata_size &&
- AV_RL32(pkt->data + 4) != MKTAG('j', 'P', ' ', ' '))) { // signature
-error:
- av_log(s, AV_LOG_ERROR, "malformed JPEG 2000 codestream\n");
- return -1;
- }
+ if (desc->nb_components > 3) {
+ avio_write(pb[3], pkt->data + ysize + 2*usize, ysize);
- avio_closep(&pb[3]);
++ ff_format_io_close(s, &pb[3]);
}
+ } else if (img->muxer) {
+ int ret;
+ AVStream *st;
+ AVPacket pkt2 = {0};
+ AVFormatContext *fmt = NULL;
+
+ av_assert0(!img->split_planes);
+
+ ret = avformat_alloc_output_context2(&fmt, NULL, img->muxer, s->filename);
+ if (ret < 0)
+ return ret;
+ st = avformat_new_stream(fmt, NULL);
+ if (!st) {
+ avformat_free_context(fmt);
+ return AVERROR(ENOMEM);
+ }
+ st->id = pkt->stream_index;
+
+ fmt->pb = pb[0];
+ if ((ret = av_copy_packet(&pkt2, pkt)) < 0 ||
+ (ret = av_dup_packet(&pkt2)) < 0 ||
+ (ret = avcodec_copy_context(st->codec, s->streams[0]->codec)) < 0 ||
+ (ret = avformat_write_header(fmt, NULL)) < 0 ||
+ (ret = av_interleaved_write_frame(fmt, &pkt2)) < 0 ||
+ (ret = av_write_trailer(fmt)) < 0) {
+ av_packet_unref(&pkt2);
+ avformat_free_context(fmt);
+ return ret;
+ }
+ av_packet_unref(&pkt2);
+ avformat_free_context(fmt);
+ } else {
avio_write(pb[0], pkt->data, pkt->size);
}
avio_flush(pb[0]);
if (!img->is_pipe) {
- avio_closep(&pb[0]);
+ ff_format_io_close(s, &pb[0]);
- ff_rename(img->tmp, filename);
+ for (i = 0; i < nb_renames; i++) {
+ ff_rename(img->tmp[i], img->target[i], s);
+ }
}
img->img_number++;
}
/**
+ * Allocate extradata with additional AV_INPUT_BUFFER_PADDING_SIZE at end
+ * which is always set to 0.
+ *
+ * @param size size of extradata
+ * @return 0 if OK, AVERROR_xxx on error
+ */
+int ff_alloc_extradata(AVCodecContext *avctx, int size);
+
+/**
+ * Allocate extradata with additional AV_INPUT_BUFFER_PADDING_SIZE at end
+ * which is always set to 0 and fill it from pb.
+ *
+ * @param size size of extradata
+ * @return >= 0 if OK, AVERROR_xxx on error
+ */
+int ff_get_extradata(AVCodecContext *avctx, AVIOContext *pb, int size);
+
+/**
+ * add frame for rfps calculation.
+ *
+ * @param dts timestamp of the i-th frame
+ * @return 0 if OK, AVERROR_xxx on error
+ */
+int ff_rfps_add_frame(AVFormatContext *ic, AVStream *st, int64_t dts);
+
+void ff_rfps_calculate(AVFormatContext *ic);
+
+/**
+ * Flags for AVFormatContext.write_uncoded_frame()
+ */
+enum AVWriteUncodedFrameFlags {
+
+ /**
+ * Query whether the feature is possible on this stream.
+ * The frame argument is ignored.
+ */
+ AV_WRITE_UNCODED_FRAME_QUERY = 0x0001,
+
+};
+
+/**
+ * Copies the whilelists from one context to the other
+ */
+int ff_copy_whitelists(AVFormatContext *dst, AVFormatContext *src);
+
+int ffio_open2_wrapper(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags,
+ const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+/**
+ * Returned by demuxers to indicate that data was consumed but discarded
+ * (ignored streams or junk data). The framework will re-call the demuxer.
+ */
+#define FFERROR_REDO FFERRTAG('R','E','D','O')
+
++/*
+ * A wrapper around AVFormatContext.io_close that should be used
+ * intead of calling the pointer directly.
+ */
+ void ff_format_io_close(AVFormatContext *s, AVIOContext **pb);
+
#endif /* AVFORMAT_INTERNAL_H */
--- /dev/null
- AVOpenCallback open_func = avctx->open_cb;
+/*
+ * Magic Lantern Video (MLV) demuxer
+ * Copyright (c) 2014 Peter Ross
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * Magic Lantern Video (MLV) demuxer
+ */
+
+#include "libavutil/eval.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/rational.h"
+#include "avformat.h"
+#include "avio_internal.h"
+#include "internal.h"
+#include "riff.h"
+
+#define MLV_VERSION "v2.0"
+
+#define MLV_VIDEO_CLASS_RAW 1
+#define MLV_VIDEO_CLASS_YUV 2
+#define MLV_VIDEO_CLASS_JPEG 3
+#define MLV_VIDEO_CLASS_H264 4
+
+#define MLV_AUDIO_CLASS_WAV 1
+
+#define MLV_CLASS_FLAG_DELTA 0x40
+#define MLV_CLASS_FLAG_LZMA 0x80
+
+typedef struct {
+ AVIOContext *pb[101];
+ int class[2];
+ int stream_index;
+ uint64_t pts;
+} MlvContext;
+
+static int probe(AVProbeData *p)
+{
+ if (AV_RL32(p->buf) == MKTAG('M','L','V','I') &&
+ AV_RL32(p->buf + 4) >= 52 &&
+ !memcmp(p->buf + 8, MLV_VERSION, 5))
+ return AVPROBE_SCORE_MAX;
+ return 0;
+}
+
+static int check_file_header(AVIOContext *pb, uint64_t guid)
+{
+ unsigned int size;
+ uint8_t version[8];
+
+ avio_skip(pb, 4);
+ size = avio_rl32(pb);
+ if (size < 52)
+ return AVERROR_INVALIDDATA;
+ avio_read(pb, version, 8);
+ if (memcmp(version, MLV_VERSION, 5) || avio_rl64(pb) != guid)
+ return AVERROR_INVALIDDATA;
+ avio_skip(pb, size - 24);
+ return 0;
+}
+
+static void read_string(AVFormatContext *avctx, AVIOContext *pb, const char *tag, int size)
+{
+ char * value = av_malloc(size + 1);
+ if (!value) {
+ avio_skip(pb, size);
+ return;
+ }
+
+ avio_read(pb, value, size);
+ if (!value[0]) {
+ av_free(value);
+ return;
+ }
+
+ value[size] = 0;
+ av_dict_set(&avctx->metadata, tag, value, AV_DICT_DONT_STRDUP_VAL);
+}
+
+static void read_uint8(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt)
+{
+ av_dict_set_int(&avctx->metadata, tag, avio_r8(pb), 0);
+}
+
+static void read_uint16(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt)
+{
+ av_dict_set_int(&avctx->metadata, tag, avio_rl16(pb), 0);
+}
+
+static void read_uint32(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt)
+{
+ av_dict_set_int(&avctx->metadata, tag, avio_rl32(pb), 0);
+}
+
+static void read_uint64(AVFormatContext *avctx, AVIOContext *pb, const char *tag, const char *fmt)
+{
+ av_dict_set_int(&avctx->metadata, tag, avio_rl64(pb), 0);
+}
+
+static int scan_file(AVFormatContext *avctx, AVStream *vst, AVStream *ast, int file)
+{
+ MlvContext *mlv = avctx->priv_data;
+ AVIOContext *pb = mlv->pb[file];
+ int ret;
+ while (!avio_feof(pb)) {
+ int type;
+ unsigned int size;
+ type = avio_rl32(pb);
+ size = avio_rl32(pb);
+ avio_skip(pb, 8); //timestamp
+ if (size < 16)
+ break;
+ size -= 16;
+ if (vst && type == MKTAG('R','A','W','I') && size >= 164) {
+ vst->codec->width = avio_rl16(pb);
+ vst->codec->height = avio_rl16(pb);
+ ret = av_image_check_size(vst->codec->width, vst->codec->height, 0, avctx);
+ if (ret < 0)
+ return ret;
+ if (avio_rl32(pb) != 1)
+ avpriv_request_sample(avctx, "raw api version");
+ avio_skip(pb, 20); // pointer, width, height, pitch, frame_size
+ vst->codec->bits_per_coded_sample = avio_rl32(pb);
+ if (vst->codec->bits_per_coded_sample < 0 ||
+ vst->codec->bits_per_coded_sample > (INT_MAX - 7) / (vst->codec->width * vst->codec->height)) {
+ av_log(avctx, AV_LOG_ERROR,
+ "invalid bits_per_coded_sample %d (size: %dx%d)\n",
+ vst->codec->bits_per_coded_sample,
+ vst->codec->width, vst->codec->height);
+ return AVERROR_INVALIDDATA;
+ }
+ avio_skip(pb, 8 + 16 + 24); // black_level, white_level, xywh, active_area, exposure_bias
+ if (avio_rl32(pb) != 0x2010100) /* RGGB */
+ avpriv_request_sample(avctx, "cfa_pattern");
+ avio_skip(pb, 80); // calibration_illuminant1, color_matrix1, dynamic_range
+ vst->codec->pix_fmt = AV_PIX_FMT_BAYER_RGGB16LE;
+ vst->codec->codec_tag = MKTAG('B', 'I', 'T', 16);
+ size -= 164;
+ } else if (ast && type == MKTAG('W', 'A', 'V', 'I') && size >= 16) {
+ ret = ff_get_wav_header(avctx, pb, ast->codec, 16, 0);
+ if (ret < 0)
+ return ret;
+ size -= 16;
+ } else if (type == MKTAG('I','N','F','O')) {
+ if (size > 0)
+ read_string(avctx, pb, "info", size);
+ continue;
+ } else if (type == MKTAG('I','D','N','T') && size >= 36) {
+ read_string(avctx, pb, "cameraName", 32);
+ read_uint32(avctx, pb, "cameraModel", "0x%"PRIx32);
+ size -= 36;
+ if (size >= 32) {
+ read_string(avctx, pb, "cameraSerial", 32);
+ size -= 32;
+ }
+ } else if (type == MKTAG('L','E','N','S') && size >= 48) {
+ read_uint16(avctx, pb, "focalLength", "%i");
+ read_uint16(avctx, pb, "focalDist", "%i");
+ read_uint16(avctx, pb, "aperture", "%i");
+ read_uint8(avctx, pb, "stabilizerMode", "%i");
+ read_uint8(avctx, pb, "autofocusMode", "%i");
+ read_uint32(avctx, pb, "flags", "0x%"PRIx32);
+ read_uint32(avctx, pb, "lensID", "%"PRIi32);
+ read_string(avctx, pb, "lensName", 32);
+ size -= 48;
+ if (size >= 32) {
+ read_string(avctx, pb, "lensSerial", 32);
+ size -= 32;
+ }
+ } else if (vst && type == MKTAG('V', 'I', 'D', 'F') && size >= 4) {
+ uint64_t pts = avio_rl32(pb);
+ ff_add_index_entry(&vst->index_entries, &vst->nb_index_entries, &vst->index_entries_allocated_size,
+ avio_tell(pb) - 20, pts, file, 0, AVINDEX_KEYFRAME);
+ size -= 4;
+ } else if (ast && type == MKTAG('A', 'U', 'D', 'F') && size >= 4) {
+ uint64_t pts = avio_rl32(pb);
+ ff_add_index_entry(&ast->index_entries, &ast->nb_index_entries, &ast->index_entries_allocated_size,
+ avio_tell(pb) - 20, pts, file, 0, AVINDEX_KEYFRAME);
+ size -= 4;
+ } else if (vst && type == MKTAG('W','B','A','L') && size >= 28) {
+ read_uint32(avctx, pb, "wb_mode", "%"PRIi32);
+ read_uint32(avctx, pb, "kelvin", "%"PRIi32);
+ read_uint32(avctx, pb, "wbgain_r", "%"PRIi32);
+ read_uint32(avctx, pb, "wbgain_g", "%"PRIi32);
+ read_uint32(avctx, pb, "wbgain_b", "%"PRIi32);
+ read_uint32(avctx, pb, "wbs_gm", "%"PRIi32);
+ read_uint32(avctx, pb, "wbs_ba", "%"PRIi32);
+ size -= 28;
+ } else if (type == MKTAG('R','T','C','I') && size >= 20) {
+ char str[32];
+ struct tm time = { 0 };
+ time.tm_sec = avio_rl16(pb);
+ time.tm_min = avio_rl16(pb);
+ time.tm_hour = avio_rl16(pb);
+ time.tm_mday = avio_rl16(pb);
+ time.tm_mon = avio_rl16(pb);
+ time.tm_year = avio_rl16(pb);
+ time.tm_wday = avio_rl16(pb);
+ time.tm_yday = avio_rl16(pb);
+ time.tm_isdst = avio_rl16(pb);
+ avio_skip(pb, 2);
+ if (strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", &time))
+ av_dict_set(&avctx->metadata, "time", str, 0);
+ size -= 20;
+ } else if (type == MKTAG('E','X','P','O') && size >= 16) {
+ av_dict_set(&avctx->metadata, "isoMode", avio_rl32(pb) ? "auto" : "manual", 0);
+ read_uint32(avctx, pb, "isoValue", "%"PRIi32);
+ read_uint32(avctx, pb, "isoAnalog", "%"PRIi32);
+ read_uint32(avctx, pb, "digitalGain", "%"PRIi32);
+ size -= 16;
+ if (size >= 8) {
+ read_uint64(avctx, pb, "shutterValue", "%"PRIi64);
+ size -= 8;
+ }
+ } else if (type == MKTAG('S','T','Y','L') && size >= 36) {
+ read_uint32(avctx, pb, "picStyleId", "%"PRIi32);
+ read_uint32(avctx, pb, "contrast", "%"PRIi32);
+ read_uint32(avctx, pb, "sharpness", "%"PRIi32);
+ read_uint32(avctx, pb, "saturation", "%"PRIi32);
+ read_uint32(avctx, pb, "colortone", "%"PRIi32);
+ read_string(avctx, pb, "picStyleName", 16);
+ size -= 36;
+ } else if (type == MKTAG('M','A','R','K')) {
+ } else if (type == MKTAG('N','U','L','L')) {
+ } else if (type == MKTAG('M','L','V','I')) { /* occurs when MLV and Mnn files are concatenated */
+ } else {
+ av_log(avctx, AV_LOG_INFO, "unsupported tag %c%c%c%c, size %u\n", type&0xFF, (type>>8)&0xFF, (type>>16)&0xFF, (type>>24)&0xFF, size);
+ }
+ avio_skip(pb, size);
+ }
+ return 0;
+}
+
+static int read_header(AVFormatContext *avctx)
+{
+ MlvContext *mlv = avctx->priv_data;
+ AVIOContext *pb = avctx->pb;
+ AVStream *vst = NULL, *ast = NULL;
+ int size, ret;
+ unsigned nb_video_frames, nb_audio_frames;
+ uint64_t guid;
+ char guidstr[32];
+
+ avio_skip(pb, 4);
+ size = avio_rl32(pb);
+ if (size < 52)
+ return AVERROR_INVALIDDATA;
+
+ avio_skip(pb, 8);
+
+ guid = avio_rl64(pb);
+ snprintf(guidstr, sizeof(guidstr), "0x%"PRIx64, guid);
+ av_dict_set(&avctx->metadata, "guid", guidstr, 0);
+
+ avio_skip(pb, 8); //fileNum, fileCount, fileFlags
+
+ mlv->class[0] = avio_rl16(pb);
+ mlv->class[1] = avio_rl16(pb);
+
+ nb_video_frames = avio_rl32(pb);
+ nb_audio_frames = avio_rl32(pb);
+
+ if (nb_video_frames && mlv->class[0]) {
+ vst = avformat_new_stream(avctx, NULL);
+ if (!vst)
+ return AVERROR(ENOMEM);
+ vst->id = 0;
+ vst->nb_frames = nb_video_frames;
+ if ((mlv->class[0] & (MLV_CLASS_FLAG_DELTA|MLV_CLASS_FLAG_LZMA)))
+ avpriv_request_sample(avctx, "compression");
+ vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
+ switch (mlv->class[0] & ~(MLV_CLASS_FLAG_DELTA|MLV_CLASS_FLAG_LZMA)) {
+ case MLV_VIDEO_CLASS_RAW:
+ vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO;
+ break;
+ case MLV_VIDEO_CLASS_YUV:
+ vst->codec->pix_fmt = AV_PIX_FMT_YUV420P;
+ vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO;
+ vst->codec->codec_tag = 0;
+ break;
+ case MLV_VIDEO_CLASS_JPEG:
+ vst->codec->codec_id = AV_CODEC_ID_MJPEG;
+ vst->codec->codec_tag = 0;
+ break;
+ case MLV_VIDEO_CLASS_H264:
+ vst->codec->codec_id = AV_CODEC_ID_H264;
+ vst->codec->codec_tag = 0;
+ break;
+ default:
+ avpriv_request_sample(avctx, "unknown video class");
+ }
+ }
+
+ if (nb_audio_frames && mlv->class[1]) {
+ ast = avformat_new_stream(avctx, NULL);
+ if (!ast)
+ return AVERROR(ENOMEM);
+ ast->id = 1;
+ ast->nb_frames = nb_audio_frames;
+ if ((mlv->class[1] & MLV_CLASS_FLAG_LZMA))
+ avpriv_request_sample(avctx, "compression");
+ if ((mlv->class[1] & ~MLV_CLASS_FLAG_LZMA) != MLV_AUDIO_CLASS_WAV)
+ avpriv_request_sample(avctx, "unknown audio class");
+
+ ast->codec->codec_type = AVMEDIA_TYPE_AUDIO;
+ avpriv_set_pts_info(ast, 33, 1, ast->codec->sample_rate);
+ }
+
+ if (vst) {
+ AVRational framerate;
+ framerate.num = avio_rl32(pb);
+ framerate.den = avio_rl32(pb);
+ avpriv_set_pts_info(vst, 64, framerate.den, framerate.num);
+ } else
+ avio_skip(pb, 8);
+
+ avio_skip(pb, size - 52);
+
+ /* scan primary file */
+ mlv->pb[100] = avctx->pb;
+ ret = scan_file(avctx, vst, ast, 100);
+ if (ret < 0)
+ return ret;
+
+ /* scan secondary files */
+ if (strlen(avctx->filename) > 2) {
+ int i;
+ char *filename = av_strdup(avctx->filename);
- if (!open_func)
- open_func = ffio_open2_wrapper;
-
+
+ if (!filename)
+ return AVERROR(ENOMEM);
+
- if (open_func(avctx, &mlv->pb[i], filename, AVIO_FLAG_READ, &avctx->interrupt_callback, NULL) < 0)
+ for (i = 0; i < 100; i++) {
+ snprintf(filename + strlen(filename) - 2, 3, "%02d", i);
- avio_closep(&mlv->pb[i]);
++ if (avctx->io_open(avctx, &mlv->pb[i], filename, AVIO_FLAG_READ, NULL) < 0)
+ break;
+ if (check_file_header(mlv->pb[i], guid) < 0) {
+ av_log(avctx, AV_LOG_WARNING, "ignoring %s; bad format or guid mismatch\n", filename);
- avio_closep(&mlv->pb[i]);
++ ff_format_io_close(avctx, &mlv->pb[i]);
+ continue;
+ }
+ av_log(avctx, AV_LOG_INFO, "scanning %s\n", filename);
+ ret = scan_file(avctx, vst, ast, i);
+ if (ret < 0) {
+ av_log(avctx, AV_LOG_WARNING, "ignoring %s; %s\n", filename, av_err2str(ret));
- avio_closep(&mlv->pb[i]);
++ ff_format_io_close(avctx, &mlv->pb[i]);
+ continue;
+ }
+ }
+ av_free(filename);
+ }
+
+ if (vst)
+ vst->duration = vst->nb_index_entries;
+ if (ast)
+ ast->duration = ast->nb_index_entries;
+
+ if ((vst && !vst->nb_index_entries) || (ast && !ast->nb_index_entries)) {
+ av_log(avctx, AV_LOG_ERROR, "no index entries found\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (vst && ast)
+ avio_seek(pb, FFMIN(vst->index_entries[0].pos, ast->index_entries[0].pos), SEEK_SET);
+ else if (vst)
+ avio_seek(pb, vst->index_entries[0].pos, SEEK_SET);
+ else if (ast)
+ avio_seek(pb, ast->index_entries[0].pos, SEEK_SET);
+
+ return 0;
+}
+
+static int read_packet(AVFormatContext *avctx, AVPacket *pkt)
+{
+ MlvContext *mlv = avctx->priv_data;
+ AVIOContext *pb;
+ AVStream *st = avctx->streams[mlv->stream_index];
+ int index, ret;
+ unsigned int size, space;
+
+ if (mlv->pts >= st->duration)
+ return AVERROR_EOF;
+
+ index = av_index_search_timestamp(st, mlv->pts, AVSEEK_FLAG_ANY);
+ if (index < 0) {
+ av_log(avctx, AV_LOG_ERROR, "could not find index entry for frame %"PRId64"\n", mlv->pts);
+ return AVERROR(EIO);
+ }
+
+ pb = mlv->pb[st->index_entries[index].size];
+ avio_seek(pb, st->index_entries[index].pos, SEEK_SET);
+
+ avio_skip(pb, 4); // blockType
+ size = avio_rl32(pb);
+ if (size < 16)
+ return AVERROR_INVALIDDATA;
+ avio_skip(pb, 12); //timestamp, frameNumber
+ if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
+ avio_skip(pb, 8); // cropPosX, cropPosY, panPosX, panPosY
+ space = avio_rl32(pb);
+ avio_skip(pb, space);
+
+ if ((mlv->class[st->id] & (MLV_CLASS_FLAG_DELTA|MLV_CLASS_FLAG_LZMA))) {
+ ret = AVERROR_PATCHWELCOME;
+ } else if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+ ret = av_get_packet(pb, pkt, (st->codec->width * st->codec->height * st->codec->bits_per_coded_sample + 7) >> 3);
+ } else { // AVMEDIA_TYPE_AUDIO
+ if (space > UINT_MAX - 24 || size < (24 + space))
+ return AVERROR_INVALIDDATA;
+ ret = av_get_packet(pb, pkt, size - (24 + space));
+ }
+
+ if (ret < 0)
+ return ret;
+
+ pkt->stream_index = mlv->stream_index;
+ pkt->pts = mlv->pts;
+
+ mlv->stream_index++;
+ if (mlv->stream_index == avctx->nb_streams) {
+ mlv->stream_index = 0;
+ mlv->pts++;
+ }
+ return 0;
+}
+
+static int read_seek(AVFormatContext *avctx, int stream_index, int64_t timestamp, int flags)
+{
+ MlvContext *mlv = avctx->priv_data;
+
+ if ((flags & AVSEEK_FLAG_FRAME) || (flags & AVSEEK_FLAG_BYTE))
+ return AVERROR(ENOSYS);
+
+ if (!avctx->pb->seekable)
+ return AVERROR(EIO);
+
+ mlv->pts = timestamp;
+ return 0;
+}
+
+static int read_close(AVFormatContext *s)
+{
+ MlvContext *mlv = s->priv_data;
+ int i;
+ for (i = 0; i < 100; i++)
+ if (mlv->pb[i])
++ ff_format_io_close(s, &mlv->pb[i]);
+ return 0;
+}
+
+AVInputFormat ff_mlv_demuxer = {
+ .name = "mlv",
+ .long_name = NULL_IF_CONFIG_SMALL("Magic Lantern Video (MLV)"),
+ .priv_data_size = sizeof(MlvContext),
+ .read_probe = probe,
+ .read_header = read_header,
+ .read_packet = read_packet,
+ .read_close = read_close,
+ .read_seek = read_seek,
+};
}
}
-static int mov_open_dref(AVFormatContext *s, AVIOContext **pb, char *src,
- MOVDref *ref)
+static int test_same_origin(const char *src, const char *ref) {
+ char src_proto[64];
+ char ref_proto[64];
+ char src_auth[256];
+ char ref_auth[256];
+ char src_host[256];
+ char ref_host[256];
+ int src_port=-1;
+ int ref_port=-1;
+
+ av_url_split(src_proto, sizeof(src_proto), src_auth, sizeof(src_auth), src_host, sizeof(src_host), &src_port, NULL, 0, src);
+ av_url_split(ref_proto, sizeof(ref_proto), ref_auth, sizeof(ref_auth), ref_host, sizeof(ref_host), &ref_port, NULL, 0, ref);
+
+ if (strlen(src) == 0) {
+ return -1;
+ } else if (strlen(src_auth) + 1 >= sizeof(src_auth) ||
+ strlen(ref_auth) + 1 >= sizeof(ref_auth) ||
+ strlen(src_host) + 1 >= sizeof(src_host) ||
+ strlen(ref_host) + 1 >= sizeof(ref_host)) {
+ return 0;
+ } else if (strcmp(src_proto, ref_proto) ||
+ strcmp(src_auth, ref_auth) ||
+ strcmp(src_host, ref_host) ||
+ src_port != ref_port) {
+ return 0;
+ } else
+ return 1;
+}
+
- static int mov_open_dref(MOVContext *c, AVIOContext **pb, const char *src, MOVDref *ref,
- AVIOInterruptCB *int_cb)
++static int mov_open_dref(MOVContext *c, AVIOContext **pb, const char *src, MOVDref *ref)
{
- AVOpenCallback open_func = c->fc->open_cb;
-
- if (!open_func)
- open_func = ffio_open2_wrapper;
-
/* try relative path, we do not try the absolute because it can leak information about our
system to an attacker */
if (ref->nlvl_to > 0 && ref->nlvl_from > 0) {
filename[src_path - src] = 0;
for (i = 1; i < ref->nlvl_from; i++)
- av_strlcat(filename, "../", 1024);
+ av_strlcat(filename, "../", sizeof(filename));
+
+ av_strlcat(filename, ref->path + l + 1, sizeof(filename));
- if (!c->use_absolute_path && !c->fc->open_cb) {
++ if (!c->use_absolute_path) {
+ int same_origin = test_same_origin(src, filename);
+
+ if (!same_origin) {
+ av_log(c->fc, AV_LOG_ERROR,
+ "Reference with mismatching origin, %s not tried for security reasons, "
+ "set demuxer option use_absolute_path to allow it anyway\n",
+ ref->path);
+ return AVERROR(ENOENT);
+ }
- av_strlcat(filename, ref->path + l + 1, 1024);
+ if(strstr(ref->path + l + 1, "..") ||
+ strstr(ref->path + l + 1, ":") ||
+ (ref->nlvl_from > 1 && same_origin < 0) ||
+ (filename[0] == '/' && src_path == src))
+ return AVERROR(ENOENT);
+ }
- if (!s->io_open(s, pb, filename, AVIO_FLAG_READ, NULL))
+ if (strlen(filename) + 1 == sizeof(filename))
+ return AVERROR(ENOENT);
- if (!open_func(c->fc, pb, filename, AVIO_FLAG_READ, int_cb, NULL))
++ if (!c->fc->io_open(c->fc, pb, filename, AVIO_FLAG_READ, NULL))
return 0;
}
- if (!open_func(c->fc, pb, ref->path, AVIO_FLAG_READ, int_cb, NULL))
- return 0;
- } else if (c->fc->open_cb) {
- if (!open_func(c->fc, pb, ref->path, AVIO_FLAG_READ, int_cb, NULL))
+ } else if (c->use_absolute_path) {
+ av_log(c->fc, AV_LOG_WARNING, "Using absolute path on user request, "
+ "this is a possible security issue\n");
++ if (!c->fc->io_open(c->fc, pb, ref->path, AVIO_FLAG_READ, NULL))
+ return 0;
+ } else {
+ av_log(c->fc, AV_LOG_ERROR,
+ "Absolute path %s not tried for security reasons, "
+ "set demuxer option use_absolute_path to allow absolute paths\n",
+ ref->path);
}
return AVERROR(ENOENT);
if (sc->dref_id-1 < sc->drefs_count && sc->drefs[sc->dref_id-1].path) {
MOVDref *dref = &sc->drefs[sc->dref_id - 1];
if (c->enable_drefs) {
- if (mov_open_dref(c, &sc->pb, c->fc->filename, dref,
- &c->fc->interrupt_callback) < 0)
- if (mov_open_dref(c->fc, &sc->pb, c->fc->filename, dref) < 0)
++ if (mov_open_dref(c, &sc->pb, c->fc->filename, dref) < 0)
av_log(c->fc, AV_LOG_ERROR,
"stream %d, error opening alias: path='%s', dir='%s', "
"filename='%s', volume='%s', nlvl_from=%d, nlvl_to=%d\n",
av_freep(&sc->drefs[j].dir);
}
av_freep(&sc->drefs);
- if (sc->pb && sc->pb != s->pb)
+
+ sc->drefs_count = 0;
+
+ if (!sc->pb_is_copied)
- avio_closep(&sc->pb);
+ ff_format_io_close(s, &sc->pb);
+ sc->pb = NULL;
av_freep(&sc->chunk_offsets);
av_freep(&sc->stsc_data);
av_freep(&sc->sample_sizes);
.version = LIBAVUTIL_VERSION_INT,
.child_next = format_child_next,
.child_class_next = format_child_class_next,
+ .category = AV_CLASS_CATEGORY_MUXER,
+ .get_category = get_category,
};
- return avio_open2(pb, url, flags, &s->interrupt_callback, options);
+ static int io_open_default(AVFormatContext *s, AVIOContext **pb,
+ const char *url, int flags, AVDictionary **options)
+ {
++#if FF_API_OLD_OPEN_CALLBACKS
++FF_DISABLE_DEPRECATION_WARNINGS
++ if (s->open_cb)
++ return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);
++FF_ENABLE_DEPRECATION_WARNINGS
++#endif
++
++ return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist);
+ }
+
+ static void io_close_default(AVFormatContext *s, AVIOContext *pb)
+ {
+ avio_close(pb);
+ }
+
static void avformat_get_context_defaults(AVFormatContext *s)
{
memset(s, 0, sizeof(AVFormatContext));
SegmentContext *seg = s->priv_data;
AVFormatContext *oc;
int i;
+ int ret;
- seg->avf = oc = avformat_alloc_context();
- if (!oc)
- return AVERROR(ENOMEM);
+ ret = avformat_alloc_output_context2(&seg->avf, seg->oformat, NULL, NULL);
+ if (ret < 0)
+ return ret;
+ oc = seg->avf;
- oc->oformat = seg->oformat;
oc->interrupt_callback = s->interrupt_callback;
+ oc->max_delay = s->max_delay;
+ av_dict_copy(&oc->metadata, s->metadata, 0);
+ oc->opaque = s->opaque;
+ oc->io_close = s->io_close;
+ oc->io_open = s->io_open;
for (i = 0; i < s->nb_streams; i++) {
AVStream *st;
if (write_header) {
avformat_free_context(oc);
- c->avf = NULL;
+ seg->avf = NULL;
if ((err = segment_mux_init(s)) < 0)
return err;
- oc = c->avf;
+ oc = seg->avf;
}
- if (c->wrap)
- c->number %= c->wrap;
+ seg->segment_idx++;
+ if ((seg->segment_idx_wrap) && (seg->segment_idx % seg->segment_idx_wrap == 0))
+ seg->segment_idx_wrap_nb++;
- if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
- s->filename, c->number++) < 0)
- return AVERROR(EINVAL);
+ if ((err = set_segment_filename(s)) < 0)
+ return err;
- if ((err = ffio_open_whitelist(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL, s->protocol_whitelist)) < 0) {
- if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0)
++ if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0) {
+ av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename);
return err;
+ }
+ if (!seg->individual_header_trailer)
+ oc->pb->seekable = 0;
if (oc->oformat->priv_class && oc->priv_data)
- av_opt_set(oc->priv_data, "resend_headers", "1", 0); /* mpegts specific */
+ av_opt_set(oc->priv_data, "mpegts_flags", "+resend_headers", 0);
if (write_header) {
if ((err = avformat_write_header(oc, NULL)) < 0)
return 0;
}
-static int segment_end(AVFormatContext *oc, int write_trailer)
+static int segment_list_open(AVFormatContext *s)
+{
+ SegmentContext *seg = s->priv_data;
+ int ret;
+
+ snprintf(seg->temp_list_filename, sizeof(seg->temp_list_filename), seg->use_rename ? "%s.tmp" : "%s", seg->list);
- ret = ffio_open_whitelist(&seg->list_pb, seg->temp_list_filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL, s->protocol_whitelist);
++ ret = s->io_open(s, &seg->list_pb, seg->temp_list_filename, AVIO_FLAG_WRITE, NULL);
+ if (ret < 0) {
+ av_log(s, AV_LOG_ERROR, "Failed to open segment list '%s'\n", seg->list);
+ return ret;
+ }
+
+ if (seg->list_type == LIST_TYPE_M3U8 && seg->segment_list_entries) {
+ SegmentListEntry *entry;
+ double max_duration = 0;
+
+ avio_printf(seg->list_pb, "#EXTM3U\n");
+ avio_printf(seg->list_pb, "#EXT-X-VERSION:3\n");
+ avio_printf(seg->list_pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", seg->segment_list_entries->index);
+ avio_printf(seg->list_pb, "#EXT-X-ALLOW-CACHE:%s\n",
+ seg->list_flags & SEGMENT_LIST_FLAG_CACHE ? "YES" : "NO");
+
+ av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%d\n",
+ seg->segment_list_entries->index);
+
+ for (entry = seg->segment_list_entries; entry; entry = entry->next)
+ max_duration = FFMAX(max_duration, entry->end_time - entry->start_time);
+ avio_printf(seg->list_pb, "#EXT-X-TARGETDURATION:%"PRId64"\n", (int64_t)ceil(max_duration));
+ } else if (seg->list_type == LIST_TYPE_FFCONCAT) {
+ avio_printf(seg->list_pb, "ffconcat version 1.0\n");
+ }
+
+ return ret;
+}
+
+static void segment_list_print_entry(AVIOContext *list_ioctx,
+ ListType list_type,
+ const SegmentListEntry *list_entry,
+ void *log_ctx)
{
+ switch (list_type) {
+ case LIST_TYPE_FLAT:
+ avio_printf(list_ioctx, "%s\n", list_entry->filename);
+ break;
+ case LIST_TYPE_CSV:
+ case LIST_TYPE_EXT:
+ print_csv_escaped_str(list_ioctx, list_entry->filename);
+ avio_printf(list_ioctx, ",%f,%f\n", list_entry->start_time, list_entry->end_time);
+ break;
+ case LIST_TYPE_M3U8:
+ avio_printf(list_ioctx, "#EXTINF:%f,\n%s\n",
+ list_entry->end_time - list_entry->start_time, list_entry->filename);
+ break;
+ case LIST_TYPE_FFCONCAT:
+ {
+ char *buf;
+ if (av_escape(&buf, list_entry->filename, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_WHITESPACE) < 0) {
+ av_log(log_ctx, AV_LOG_WARNING,
+ "Error writing list entry '%s' in list file\n", list_entry->filename);
+ return;
+ }
+ avio_printf(list_ioctx, "file %s\n", buf);
+ av_free(buf);
+ break;
+ }
+ default:
+ av_assert0(!"Invalid list type");
+ }
+}
+
+static int segment_end(AVFormatContext *s, int write_trailer, int is_last)
+{
+ SegmentContext *seg = s->priv_data;
+ AVFormatContext *oc = seg->avf;
int ret = 0;
av_write_frame(oc, NULL); /* Flush any buffered data (fragmented mp4) */
if (write_trailer)
- av_write_trailer(oc);
+ ret = av_write_trailer(oc);
+
+ if (ret < 0)
+ av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n",
+ oc->filename);
+
+ if (seg->list) {
+ if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) {
+ SegmentListEntry *entry = av_mallocz(sizeof(*entry));
+ if (!entry) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ /* append new element */
+ memcpy(entry, &seg->cur_entry, sizeof(*entry));
+ entry->filename = av_strdup(entry->filename);
+ if (!seg->segment_list_entries)
+ seg->segment_list_entries = seg->segment_list_entries_end = entry;
+ else
+ seg->segment_list_entries_end->next = entry;
+ seg->segment_list_entries_end = entry;
+
+ /* drop first item */
+ if (seg->list_size && seg->segment_count >= seg->list_size) {
+ entry = seg->segment_list_entries;
+ seg->segment_list_entries = seg->segment_list_entries->next;
+ av_freep(&entry->filename);
+ av_freep(&entry);
+ }
+
+ if ((ret = segment_list_open(s)) < 0)
+ goto end;
+ for (entry = seg->segment_list_entries; entry; entry = entry->next)
+ segment_list_print_entry(seg->list_pb, seg->list_type, entry, s);
+ if (seg->list_type == LIST_TYPE_M3U8 && is_last)
+ avio_printf(seg->list_pb, "#EXT-X-ENDLIST\n");
- avio_closep(&seg->list_pb);
++ ff_format_io_close(s, &seg->list_pb);
+ if (seg->use_rename)
+ ff_rename(seg->temp_list_filename, seg->list, s);
+ } else {
+ segment_list_print_entry(seg->list_pb, seg->list_type, &seg->cur_entry, s);
+ avio_flush(seg->list_pb);
+ }
+ }
+
+ av_log(s, AV_LOG_VERBOSE, "segment:'%s' count:%d ended\n",
+ seg->avf->filename, seg->segment_count);
+ seg->segment_count++;
+
+end:
- avio_closep(&oc->pb);
+ ff_format_io_close(oc, &oc->pb);
return ret;
}
static void seg_free_context(SegmentContext *seg)
{
- avio_closep(&seg->list_pb);
- ff_format_io_close(seg->avf, &seg->pb);
++ ff_format_io_close(seg->avf, &seg->list_pb);
avformat_free_context(seg->avf);
seg->avf = NULL;
}
goto fail;
oc = seg->avf;
- if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
- s->filename, seg->number++) < 0) {
- ret = AVERROR(EINVAL);
+ if ((ret = set_segment_filename(s)) < 0)
goto fail;
- }
if (seg->write_header_trailer) {
- if ((ret = ffio_open_whitelist(&oc->pb, seg->header_filename ? seg->header_filename : oc->filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL, s->protocol_whitelist)) < 0) {
- if ((ret = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0)
++ if ((ret = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0) {
+ av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename);
goto fail;
+ }
+ if (!seg->individual_header_trailer)
+ oc->pb->seekable = 0;
} else {
if ((ret = open_null_ctx(&oc->pb)) < 0)
goto fail;
}
- if ((ret = avformat_write_header(oc, NULL)) < 0) {
+ av_dict_copy(&options, seg->format_options, 0);
+ ret = avformat_write_header(oc, &options);
+ if (av_dict_count(options)) {
+ av_log(s, AV_LOG_ERROR,
+ "Some of the provided format options in '%s' are not recognized\n", seg->format_options_str);
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ if (ret < 0) {
- avio_closep(&oc->pb);
+ ff_format_io_close(oc, &oc->pb);
goto fail;
}
+ seg->segment_frame_count = 0;
- if (!seg->write_header_trailer) {
- close_null_ctx(oc->pb);
- if ((ret = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0)
- goto fail;
+ av_assert0(s->nb_streams == oc->nb_streams);
+ for (i = 0; i < s->nb_streams; i++) {
+ AVStream *inner_st = oc->streams[i];
+ AVStream *outer_st = s->streams[i];
+ avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
}
- if (seg->list) {
- if (seg->list_type == LIST_HLS) {
- if ((ret = segment_hls_window(s, 0)) < 0)
- goto fail;
+ if (oc->avoid_negative_ts > 0 && s->avoid_negative_ts < 0)
+ s->avoid_negative_ts = 1;
+
+ if (!seg->write_header_trailer || seg->header_filename) {
+ if (seg->header_filename) {
+ av_write_frame(oc, NULL);
- avio_closep(&oc->pb);
++ ff_format_io_close(oc, &oc->pb);
} else {
- avio_printf(seg->pb, "%s\n", oc->filename);
- avio_flush(seg->pb);
+ close_null_ctxp(&oc->pb);
}
- if ((ret = ffio_open_whitelist(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL, s->protocol_whitelist)) < 0)
++ if ((ret = oc->io_open(oc, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0)
+ goto fail;
+ if (!seg->individual_header_trailer)
+ oc->pb->seekable = 0;
}
fail:
if ((ret = open_null_ctx(&oc->pb)) < 0)
goto fail;
ret = av_write_trailer(oc);
- close_null_ctx(oc->pb);
+ close_null_ctxp(&oc->pb);
} else {
- ret = segment_end(oc, 1);
+ ret = segment_end(s, 1, 1);
}
-
- if (ret < 0)
- goto fail;
-
- if (seg->list && seg->list_type == LIST_HLS) {
- if ((ret = segment_hls_window(s, 1) < 0))
- goto fail;
+fail:
+ if (seg->list)
- avio_closep(&seg->list_pb);
++ ff_format_io_close(s, &seg->list_pb);
+
+ av_dict_free(&seg->format_options);
+ av_opt_free(seg);
+ av_freep(&seg->times);
+ av_freep(&seg->frames);
+ av_freep(&seg->cur_entry.filename);
+
+ cur = seg->segment_list_entries;
+ while (cur) {
+ next = cur->next;
+ av_freep(&cur->filename);
+ av_free(cur);
+ cur = next;
}
-fail:
- ff_format_io_close(s, &seg->pb);
avformat_free_context(oc);
+ seg->avf = NULL;
return ret;
}
}
avio_printf(out, "</SmoothStreamingMedia>\n");
avio_flush(out);
- avio_close(out);
+ ff_format_io_close(s, &out);
- return ff_rename(temp_filename, filename);
+ return ff_rename(temp_filename, filename, s);
}
static int ism_write_header(AVFormatContext *s)
--- /dev/null
- if ((ret = ffio_open_whitelist(&avf2->pb, filename, AVIO_FLAG_WRITE,
- &avf->interrupt_callback, NULL,
- avf->protocol_whitelist)) < 0) {
+/*
+ * Tee pseudo-muxer
+ * Copyright (c) 2012 Nicolas George
+ *
+ * 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 "libavutil/avutil.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
++#include "internal.h"
+#include "avformat.h"
+#include "avio_internal.h"
+
+#define MAX_SLAVES 16
+
+typedef struct {
+ AVFormatContext *avf;
+ AVBitStreamFilterContext **bsfs; ///< bitstream filters per stream
+
+ /** map from input to output streams indexes,
+ * disabled output streams are set to -1 */
+ int *stream_map;
+} TeeSlave;
+
+typedef struct TeeContext {
+ const AVClass *class;
+ unsigned nb_slaves;
+ TeeSlave slaves[MAX_SLAVES];
+} TeeContext;
+
+static const char *const slave_delim = "|";
+static const char *const slave_opt_open = "[";
+static const char *const slave_opt_close = "]";
+static const char *const slave_opt_delim = ":]"; /* must have the close too */
+static const char *const slave_bsfs_spec_sep = "/";
+static const char *const slave_select_sep = ",";
+
+static const AVClass tee_muxer_class = {
+ .class_name = "Tee muxer",
+ .item_name = av_default_item_name,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+static int parse_slave_options(void *log, char *slave,
+ AVDictionary **options, char **filename)
+{
+ const char *p;
+ char *key, *val;
+ int ret;
+
+ if (!strspn(slave, slave_opt_open)) {
+ *filename = slave;
+ return 0;
+ }
+ p = slave + 1;
+ if (strspn(p, slave_opt_close)) {
+ *filename = (char *)p + 1;
+ return 0;
+ }
+ while (1) {
+ ret = av_opt_get_key_value(&p, "=", slave_opt_delim, 0, &key, &val);
+ if (ret < 0) {
+ av_log(log, AV_LOG_ERROR, "No option found near \"%s\"\n", p);
+ goto fail;
+ }
+ ret = av_dict_set(options, key, val,
+ AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
+ if (ret < 0)
+ goto fail;
+ if (strspn(p, slave_opt_close))
+ break;
+ p++;
+ }
+ *filename = (char *)p + 1;
+ return 0;
+
+fail:
+ av_dict_free(options);
+ return ret;
+}
+
+/**
+ * Parse list of bitstream filters and add them to the list of filters
+ * pointed to by bsfs.
+ *
+ * The list must be specified in the form:
+ * BSFS ::= BSF[,BSFS]
+ */
+static int parse_bsfs(void *log_ctx, const char *bsfs_spec,
+ AVBitStreamFilterContext **bsfs)
+{
+ char *bsf_name, *buf, *dup, *saveptr;
+ int ret = 0;
+
+ if (!(dup = buf = av_strdup(bsfs_spec)))
+ return AVERROR(ENOMEM);
+
+ while (bsf_name = av_strtok(buf, ",", &saveptr)) {
+ AVBitStreamFilterContext *bsf = av_bitstream_filter_init(bsf_name);
+
+ if (!bsf) {
+ av_log(log_ctx, AV_LOG_ERROR,
+ "Cannot initialize bitstream filter with name '%s', "
+ "unknown filter or internal error happened\n",
+ bsf_name);
+ ret = AVERROR_UNKNOWN;
+ goto end;
+ }
+
+ /* append bsf context to the list of bsf contexts */
+ *bsfs = bsf;
+ bsfs = &bsf->next;
+
+ buf = NULL;
+ }
+
+end:
+ av_free(dup);
+ return ret;
+}
+
+static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave)
+{
+ int i, ret;
+ AVDictionary *options = NULL;
+ AVDictionaryEntry *entry;
+ char *filename;
+ char *format = NULL, *select = NULL;
+ AVFormatContext *avf2 = NULL;
+ AVStream *st, *st2;
+ int stream_count;
+ int fullret;
+ char *subselect = NULL, *next_subselect = NULL, *first_subselect = NULL, *tmp_select = NULL;
+
+ if ((ret = parse_slave_options(avf, slave, &options, &filename)) < 0)
+ return ret;
+
+#define STEAL_OPTION(option, field) do { \
+ if ((entry = av_dict_get(options, option, NULL, 0))) { \
+ field = entry->value; \
+ entry->value = NULL; /* prevent it from being freed */ \
+ av_dict_set(&options, option, NULL, 0); \
+ } \
+ } while (0)
+
+ STEAL_OPTION("f", format);
+ STEAL_OPTION("select", select);
+
+ ret = avformat_alloc_output_context2(&avf2, NULL, format, filename);
+ if (ret < 0)
+ goto end;
+ av_dict_copy(&avf2->metadata, avf->metadata, 0);
++ avf2->opaque = avf->opaque;
++ avf2->io_open = avf->io_open;
++ avf2->io_close = avf->io_close;
+
+ tee_slave->stream_map = av_calloc(avf->nb_streams, sizeof(*tee_slave->stream_map));
+ if (!tee_slave->stream_map) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ stream_count = 0;
+ for (i = 0; i < avf->nb_streams; i++) {
+ st = avf->streams[i];
+ if (select) {
+ tmp_select = av_strdup(select); // av_strtok is destructive so we regenerate it in each loop
+ if (!tmp_select) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+ fullret = 0;
+ first_subselect = tmp_select;
+ next_subselect = NULL;
+ while (subselect = av_strtok(first_subselect, slave_select_sep, &next_subselect)) {
+ first_subselect = NULL;
+
+ ret = avformat_match_stream_specifier(avf, avf->streams[i], subselect);
+ if (ret < 0) {
+ av_log(avf, AV_LOG_ERROR,
+ "Invalid stream specifier '%s' for output '%s'\n",
+ subselect, slave);
+ goto end;
+ }
+ if (ret != 0) {
+ fullret = 1; // match
+ break;
+ }
+ }
+ av_freep(&tmp_select);
+
+ if (fullret == 0) { /* no match */
+ tee_slave->stream_map[i] = -1;
+ continue;
+ }
+ }
+ tee_slave->stream_map[i] = stream_count++;
+
+ if (!(st2 = avformat_new_stream(avf2, NULL))) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+ st2->id = st->id;
+ st2->r_frame_rate = st->r_frame_rate;
+ st2->time_base = st->time_base;
+ st2->start_time = st->start_time;
+ st2->duration = st->duration;
+ st2->nb_frames = st->nb_frames;
+ st2->disposition = st->disposition;
+ st2->sample_aspect_ratio = st->sample_aspect_ratio;
+ st2->avg_frame_rate = st->avg_frame_rate;
+ av_dict_copy(&st2->metadata, st->metadata, 0);
+ if ((ret = avcodec_copy_context(st2->codec, st->codec)) < 0)
+ goto end;
+ }
+
+ if (!(avf2->oformat->flags & AVFMT_NOFILE)) {
- avio_closep(&avf2->pb);
++ if ((ret = avf2->io_open(avf2, &avf2->pb, filename, AVIO_FLAG_WRITE, NULL)) < 0) {
+ av_log(avf, AV_LOG_ERROR, "Slave '%s': error opening: %s\n",
+ slave, av_err2str(ret));
+ goto end;
+ }
+ }
+
+ if ((ret = avformat_write_header(avf2, &options)) < 0) {
+ av_log(avf, AV_LOG_ERROR, "Slave '%s': error writing header: %s\n",
+ slave, av_err2str(ret));
+ goto end;
+ }
+
+ tee_slave->avf = avf2;
+ tee_slave->bsfs = av_calloc(avf2->nb_streams, sizeof(TeeSlave));
+ if (!tee_slave->bsfs) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ entry = NULL;
+ while (entry = av_dict_get(options, "bsfs", NULL, AV_DICT_IGNORE_SUFFIX)) {
+ const char *spec = entry->key + strlen("bsfs");
+ if (*spec) {
+ if (strspn(spec, slave_bsfs_spec_sep) != 1) {
+ av_log(avf, AV_LOG_ERROR,
+ "Specifier separator in '%s' is '%c', but only characters '%s' "
+ "are allowed\n", entry->key, *spec, slave_bsfs_spec_sep);
+ return AVERROR(EINVAL);
+ }
+ spec++; /* consume separator */
+ }
+
+ for (i = 0; i < avf2->nb_streams; i++) {
+ ret = avformat_match_stream_specifier(avf2, avf2->streams[i], spec);
+ if (ret < 0) {
+ av_log(avf, AV_LOG_ERROR,
+ "Invalid stream specifier '%s' in bsfs option '%s' for slave "
+ "output '%s'\n", spec, entry->key, filename);
+ goto end;
+ }
+
+ if (ret > 0) {
+ av_log(avf, AV_LOG_DEBUG, "spec:%s bsfs:%s matches stream %d of slave "
+ "output '%s'\n", spec, entry->value, i, filename);
+ if (tee_slave->bsfs[i]) {
+ av_log(avf, AV_LOG_WARNING,
+ "Duplicate bsfs specification associated to stream %d of slave "
+ "output '%s', filters will be ignored\n", i, filename);
+ continue;
+ }
+ ret = parse_bsfs(avf, entry->value, &tee_slave->bsfs[i]);
+ if (ret < 0) {
+ av_log(avf, AV_LOG_ERROR,
+ "Error parsing bitstream filter sequence '%s' associated to "
+ "stream %d of slave output '%s'\n", entry->value, i, filename);
+ goto end;
+ }
+ }
+ }
+
+ av_dict_set(&options, entry->key, NULL, 0);
+ }
+
+ if (options) {
+ entry = NULL;
+ while ((entry = av_dict_get(options, "", entry, AV_DICT_IGNORE_SUFFIX)))
+ av_log(avf2, AV_LOG_ERROR, "Unknown option '%s'\n", entry->key);
+ ret = AVERROR_OPTION_NOT_FOUND;
+ goto end;
+ }
+
+end:
+ av_free(format);
+ av_free(select);
+ av_dict_free(&options);
+ av_freep(&tmp_select);
+ return ret;
+}
+
+static void close_slaves(AVFormatContext *avf)
+{
+ TeeContext *tee = avf->priv_data;
+ AVFormatContext *avf2;
+ unsigned i, j;
+
+ for (i = 0; i < tee->nb_slaves; i++) {
+ avf2 = tee->slaves[i].avf;
+
+ for (j = 0; j < avf2->nb_streams; j++) {
+ AVBitStreamFilterContext *bsf_next, *bsf = tee->slaves[i].bsfs[j];
+ while (bsf) {
+ bsf_next = bsf->next;
+ av_bitstream_filter_close(bsf);
+ bsf = bsf_next;
+ }
+ }
+ av_freep(&tee->slaves[i].stream_map);
+ av_freep(&tee->slaves[i].bsfs);
+
- if (!(avf2->oformat->flags & AVFMT_NOFILE)) {
- if ((ret = avio_closep(&avf2->pb)) < 0)
- if (!ret_all)
- ret_all = ret;
- }
++ ff_format_io_close(avf2, &avf2->pb);
+ avformat_free_context(avf2);
+ tee->slaves[i].avf = NULL;
+ }
+}
+
+static void log_slave(TeeSlave *slave, void *log_ctx, int log_level)
+{
+ int i;
+ av_log(log_ctx, log_level, "filename:'%s' format:%s\n",
+ slave->avf->filename, slave->avf->oformat->name);
+ for (i = 0; i < slave->avf->nb_streams; i++) {
+ AVStream *st = slave->avf->streams[i];
+ AVBitStreamFilterContext *bsf = slave->bsfs[i];
+
+ av_log(log_ctx, log_level, " stream:%d codec:%s type:%s",
+ i, avcodec_get_name(st->codec->codec_id),
+ av_get_media_type_string(st->codec->codec_type));
+ if (bsf) {
+ av_log(log_ctx, log_level, " bsfs:");
+ while (bsf) {
+ av_log(log_ctx, log_level, "%s%s",
+ bsf->filter->name, bsf->next ? "," : "");
+ bsf = bsf->next;
+ }
+ }
+ av_log(log_ctx, log_level, "\n");
+ }
+}
+
+static int tee_write_header(AVFormatContext *avf)
+{
+ TeeContext *tee = avf->priv_data;
+ unsigned nb_slaves = 0, i;
+ const char *filename = avf->filename;
+ char *slaves[MAX_SLAVES];
+ int ret;
+
+ while (*filename) {
+ if (nb_slaves == MAX_SLAVES) {
+ av_log(avf, AV_LOG_ERROR, "Maximum %d slave muxers reached.\n",
+ MAX_SLAVES);
+ ret = AVERROR_PATCHWELCOME;
+ goto fail;
+ }
+ if (!(slaves[nb_slaves++] = av_get_token(&filename, slave_delim))) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ if (strspn(filename, slave_delim))
+ filename++;
+ }
+
+ for (i = 0; i < nb_slaves; i++) {
+ if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0)
+ goto fail;
+ log_slave(&tee->slaves[i], avf, AV_LOG_VERBOSE);
+ av_freep(&slaves[i]);
+ }
+
+ tee->nb_slaves = nb_slaves;
+
+ for (i = 0; i < avf->nb_streams; i++) {
+ int j, mapped = 0;
+ for (j = 0; j < tee->nb_slaves; j++)
+ mapped += tee->slaves[j].stream_map[i] >= 0;
+ if (!mapped)
+ av_log(avf, AV_LOG_WARNING, "Input stream #%d is not mapped "
+ "to any slave.\n", i);
+ }
+ return 0;
+
+fail:
+ for (i = 0; i < nb_slaves; i++)
+ av_freep(&slaves[i]);
+ close_slaves(avf);
+ return ret;
+}
+
+static int tee_write_trailer(AVFormatContext *avf)
+{
+ TeeContext *tee = avf->priv_data;
+ AVFormatContext *avf2;
+ int ret_all = 0, ret;
+ unsigned i;
+
+ for (i = 0; i < tee->nb_slaves; i++) {
+ avf2 = tee->slaves[i].avf;
+ if ((ret = av_write_trailer(avf2)) < 0)
+ if (!ret_all)
+ ret_all = ret;
++ if (!(avf2->oformat->flags & AVFMT_NOFILE))
++ ff_format_io_close(avf2, &avf2->pb);
+ }
+ close_slaves(avf);
+ return ret_all;
+}
+
+static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt)
+{
+ TeeContext *tee = avf->priv_data;
+ AVFormatContext *avf2;
+ AVPacket pkt2;
+ int ret_all = 0, ret;
+ unsigned i, s;
+ int s2;
+ AVRational tb, tb2;
+
+ for (i = 0; i < tee->nb_slaves; i++) {
+ avf2 = tee->slaves[i].avf;
+ s = pkt->stream_index;
+ s2 = tee->slaves[i].stream_map[s];
+ if (s2 < 0)
+ continue;
+
+ if ((ret = av_copy_packet(&pkt2, pkt)) < 0 ||
+ (ret = av_dup_packet(&pkt2))< 0)
+ if (!ret_all) {
+ ret_all = ret;
+ continue;
+ }
+ tb = avf ->streams[s ]->time_base;
+ tb2 = avf2->streams[s2]->time_base;
+ pkt2.pts = av_rescale_q(pkt->pts, tb, tb2);
+ pkt2.dts = av_rescale_q(pkt->dts, tb, tb2);
+ pkt2.duration = av_rescale_q(pkt->duration, tb, tb2);
+ pkt2.stream_index = s2;
+
+ if ((ret = av_apply_bitstream_filters(avf2->streams[s2]->codec, &pkt2,
+ tee->slaves[i].bsfs[s2])) < 0 ||
+ (ret = av_interleaved_write_frame(avf2, &pkt2)) < 0)
+ if (!ret_all)
+ ret_all = ret;
+ }
+ return ret_all;
+}
+
+AVOutputFormat ff_tee_muxer = {
+ .name = "tee",
+ .long_name = NULL_IF_CONFIG_SMALL("Multiple muxer tee"),
+ .priv_data_size = sizeof(TeeContext),
+ .write_header = tee_write_header,
+ .write_trailer = tee_write_trailer,
+ .write_packet = tee_write_packet,
+ .priv_class = &tee_muxer_class,
+ .flags = AVFMT_NOFILE,
+};
const char *avformat_license(void)
{
#define LICENSE_PREFIX "libavformat license: "
- return LICENSE_PREFIX LIBAV_LICENSE + sizeof(LICENSE_PREFIX) - 1;
+ return LICENSE_PREFIX FFMPEG_LICENSE + sizeof(LICENSE_PREFIX) - 1;
+}
+
+#define RELATIVE_TS_BASE (INT64_MAX - (1LL<<48))
+
+static int is_relative(int64_t ts) {
+ return ts > (RELATIVE_TS_BASE - (1LL<<48));
+}
+
+/**
+ * Wrap a given time stamp, if there is an indication for an overflow
+ *
+ * @param st stream
+ * @param timestamp the time stamp to wrap
+ * @return resulting time stamp
+ */
+static int64_t wrap_timestamp(AVStream *st, int64_t timestamp)
+{
+ if (st->pts_wrap_behavior != AV_PTS_WRAP_IGNORE &&
+ st->pts_wrap_reference != AV_NOPTS_VALUE && timestamp != AV_NOPTS_VALUE) {
+ if (st->pts_wrap_behavior == AV_PTS_WRAP_ADD_OFFSET &&
+ timestamp < st->pts_wrap_reference)
+ return timestamp + (1ULL << st->pts_wrap_bits);
+ else if (st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET &&
+ timestamp >= st->pts_wrap_reference)
+ return timestamp - (1ULL << st->pts_wrap_bits);
+ }
+ return timestamp;
+}
+
+MAKE_ACCESSORS(AVStream, stream, AVRational, r_frame_rate)
+MAKE_ACCESSORS(AVStream, stream, char *, recommended_encoder_configuration)
+MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, video_codec)
+MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, audio_codec)
+MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, subtitle_codec)
+MAKE_ACCESSORS(AVFormatContext, format, AVCodec *, data_codec)
+MAKE_ACCESSORS(AVFormatContext, format, int, metadata_header_padding)
+MAKE_ACCESSORS(AVFormatContext, format, void *, opaque)
+MAKE_ACCESSORS(AVFormatContext, format, av_format_control_message, control_message_cb)
++#if FF_API_OLD_OPEN_CALLBACKS
++FF_DISABLE_DEPRECATION_WARNINGS
+MAKE_ACCESSORS(AVFormatContext, format, AVOpenCallback, open_cb)
++FF_ENABLE_DEPRECATION_WARNINGS
++#endif
+
+int64_t av_stream_get_end_pts(const AVStream *st)
+{
+ if (st->priv_pts) {
+ return st->priv_pts->val;
+ } else
+ return AV_NOPTS_VALUE;
+}
+
+struct AVCodecParserContext *av_stream_get_parser(const AVStream *st)
+{
+ return st->parser;
+}
+
+void av_format_inject_global_side_data(AVFormatContext *s)
+{
+ int i;
+ s->internal->inject_global_side_data = 1;
+ for (i = 0; i < s->nb_streams; i++) {
+ AVStream *st = s->streams[i];
+ st->inject_global_side_data = 1;
+ }
+}
+
+int ff_copy_whitelists(AVFormatContext *dst, AVFormatContext *src)
+{
+ av_assert0(!dst->codec_whitelist &&
+ !dst->format_whitelist &&
+ !dst->protocol_whitelist);
+ dst-> codec_whitelist = av_strdup(src->codec_whitelist);
+ dst->format_whitelist = av_strdup(src->format_whitelist);
+ dst->protocol_whitelist = av_strdup(src->protocol_whitelist);
+ if ( (src-> codec_whitelist && !dst-> codec_whitelist)
+ || (src-> format_whitelist && !dst-> format_whitelist)
+ || (src->protocol_whitelist && !dst->protocol_whitelist)) {
+ av_log(dst, AV_LOG_ERROR, "Failed to duplicate whitelist\n");
+ return AVERROR(ENOMEM);
+ }
+ return 0;
+}
+
+static const AVCodec *find_decoder(AVFormatContext *s, AVStream *st, enum AVCodecID codec_id)
+{
+ if (st->codec->codec)
+ return st->codec->codec;
+
+ switch (st->codec->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ if (s->video_codec) return s->video_codec;
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ if (s->audio_codec) return s->audio_codec;
+ break;
+ case AVMEDIA_TYPE_SUBTITLE:
+ if (s->subtitle_codec) return s->subtitle_codec;
+ break;
+ }
+
+ return avcodec_find_decoder(codec_id);
+}
+
+int av_format_get_probe_score(const AVFormatContext *s)
+{
+ return s->probe_score;
}
/* an arbitrarily chosen "sane" max packet size -- 50M */
}
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
- (!s->iformat && (s->iformat = av_probe_input_format(&pd, 0))))
- return 0;
+ (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
+ return score;
- if ((ret = ffio_open_whitelist(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags,
- &s->interrupt_callback, options,
- s->protocol_whitelist)) < 0)
- if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ,
- &s->interrupt_callback, options)) < 0)
++ if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
+
if (s->iformat)
return 0;
- return av_probe_input_buffer(s->pb, &s->iformat, filename,
- s, 0, s->probesize);
+ return av_probe_input_buffer2(s->pb, &s->iformat, filename,
+ s, 0, s->format_probesize);
}
static int add_to_pktbuf(AVPacketList **packet_buffer, AVPacket *pkt,
return data;
}
+int ff_stream_add_bitstream_filter(AVStream *st, const char *name, const char *args)
+{
+ AVBitStreamFilterContext *bsfc = NULL;
+ AVBitStreamFilterContext **dest = &st->internal->bsfc;
+ while (*dest && (*dest)->next)
+ dest = &(*dest)->next;
+
+ if (!(bsfc = av_bitstream_filter_init(name))) {
+ av_log(NULL, AV_LOG_ERROR, "Unknown bitstream filter '%s'\n", name);
+ return AVERROR(EINVAL);
+ }
+ if (args && !(bsfc->args = av_strdup(args))) {
+ av_bitstream_filter_close(bsfc);
+ return AVERROR(ENOMEM);
+ }
+ av_log(st->codec, AV_LOG_VERBOSE,
+ "Automatically inserted bitstream filter '%s'; args='%s'\n",
+ name, args ? args : "");
+ *dest = bsfc;
+ return 1;
+}
+
+int av_apply_bitstream_filters(AVCodecContext *codec, AVPacket *pkt,
+ AVBitStreamFilterContext *bsfc)
+{
+ int ret = 0;
+ while (bsfc) {
+ AVPacket new_pkt = *pkt;
+ int a = av_bitstream_filter_filter(bsfc, codec, NULL,
+ &new_pkt.data, &new_pkt.size,
+ pkt->data, pkt->size,
+ pkt->flags & AV_PKT_FLAG_KEY);
+ if(a == 0 && new_pkt.data != pkt->data) {
+ uint8_t *t = av_malloc(new_pkt.size + AV_INPUT_BUFFER_PADDING_SIZE); //the new should be a subset of the old so cannot overflow
+ if (t) {
+ memcpy(t, new_pkt.data, new_pkt.size);
+ memset(t + new_pkt.size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+ new_pkt.data = t;
+ new_pkt.buf = NULL;
+ a = 1;
+ } else {
+ a = AVERROR(ENOMEM);
+ }
+ }
+ if (a > 0) {
+ new_pkt.buf = av_buffer_create(new_pkt.data, new_pkt.size,
+ av_buffer_default_free, NULL, 0);
+ if (new_pkt.buf) {
+ pkt->side_data = NULL;
+ pkt->side_data_elems = 0;
+ av_packet_unref(pkt);
+ } else {
+ av_freep(&new_pkt.data);
+ a = AVERROR(ENOMEM);
+ }
+ }
+ if (a < 0) {
+ av_log(codec, AV_LOG_ERROR,
+ "Failed to open bitstream filter %s for stream %d with codec %s",
+ bsfc->filter->name, pkt->stream_index,
+ codec->codec ? codec->codec->name : "copy");
+ ret = a;
+ break;
+ }
+ *pkt = new_pkt;
+
+ bsfc = bsfc->next;
+ }
+ return ret;
+}
++
+ void ff_format_io_close(AVFormatContext *s, AVIOContext **pb)
+ {
+ if (*pb)
+ s->io_close(s, *pb);
+ *pb = NULL;
+ }
#include "libavutil/version.h"
-#define LIBAVFORMAT_VERSION_MAJOR 57
-#define LIBAVFORMAT_VERSION_MINOR 3
-#define LIBAVFORMAT_VERSION_MICRO 0
+#define LIBAVFORMAT_VERSION_MAJOR 57
- #define LIBAVFORMAT_VERSION_MINOR 24
- #define LIBAVFORMAT_VERSION_MICRO 101
++#define LIBAVFORMAT_VERSION_MINOR 25
++#define LIBAVFORMAT_VERSION_MICRO 100
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
LIBAVFORMAT_VERSION_MINOR, \
#ifndef FF_API_COMPUTE_PKT_FIELDS2
#define FF_API_COMPUTE_PKT_FIELDS2 (LIBAVFORMAT_VERSION_MAJOR < 58)
#endif
++#ifndef FF_API_OLD_OPEN_CALLBACKS
++#define FF_API_OLD_OPEN_CALLBACKS (LIBAVFORMAT_VERSION_MAJOR < 58)
++#endif
+#ifndef FF_API_R_FRAME_RATE
+#define FF_API_R_FRAME_RATE 1
+#endif
#endif /* AVFORMAT_VERSION_H */
--- /dev/null
- ret = ffio_open_whitelist(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL, s->protocol_whitelist);
+/*
+ * Copyright (c) 2015, Vignesh Venkatasubramanian
+ *
+ * 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
+ */
+
+/**
+ * @file WebM Chunk Muxer
+ * The chunk muxer enables writing WebM Live chunks where there is a header
+ * chunk, followed by data chunks where each Cluster is written out as a Chunk.
+ */
+
+#include <float.h>
+#include <time.h>
+
+#include "avformat.h"
+#include "avio.h"
+#include "avio_internal.h"
+#include "internal.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/log.h"
+#include "libavutil/opt.h"
+#include "libavutil/avstring.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/mathematics.h"
+#include "libavutil/time.h"
+#include "libavutil/time_internal.h"
+#include "libavutil/timestamp.h"
+
+#define MAX_FILENAME_SIZE 1024
+
+typedef struct WebMChunkContext {
+ const AVClass *class;
+ int chunk_start_index;
+ char *header_filename;
+ int chunk_duration;
+ int chunk_index;
+ uint64_t duration_written;
+ int prev_pts;
+ AVOutputFormat *oformat;
+ AVFormatContext *avf;
+} WebMChunkContext;
+
+static int chunk_mux_init(AVFormatContext *s)
+{
+ WebMChunkContext *wc = s->priv_data;
+ AVFormatContext *oc;
+ int ret;
+
+ ret = avformat_alloc_output_context2(&wc->avf, wc->oformat, NULL, NULL);
+ if (ret < 0)
+ return ret;
+ oc = wc->avf;
+
+ oc->interrupt_callback = s->interrupt_callback;
+ oc->max_delay = s->max_delay;
+ av_dict_copy(&oc->metadata, s->metadata, 0);
+
+ *(const AVClass**)oc->priv_data = oc->oformat->priv_class;
+ av_opt_set_defaults(oc->priv_data);
+ av_opt_set_int(oc->priv_data, "dash", 1, 0);
+ av_opt_set_int(oc->priv_data, "cluster_time_limit", wc->chunk_duration, 0);
+ av_opt_set_int(oc->priv_data, "live", 1, 0);
+
+ oc->streams = s->streams;
+ oc->nb_streams = s->nb_streams;
+
+ return 0;
+}
+
+static int get_chunk_filename(AVFormatContext *s, int is_header, char *filename)
+{
+ WebMChunkContext *wc = s->priv_data;
+ AVFormatContext *oc = wc->avf;
+ if (!filename) {
+ return AVERROR(EINVAL);
+ }
+ if (is_header) {
+ if (!wc->header_filename) {
+ return AVERROR(EINVAL);
+ }
+ av_strlcpy(filename, wc->header_filename, strlen(wc->header_filename) + 1);
+ } else {
+ if (av_get_frame_filename(filename, MAX_FILENAME_SIZE,
+ s->filename, wc->chunk_index - 1) < 0) {
+ av_log(oc, AV_LOG_ERROR, "Invalid chunk filename template '%s'\n", s->filename);
+ return AVERROR(EINVAL);
+ }
+ }
+ return 0;
+}
+
+static int webm_chunk_write_header(AVFormatContext *s)
+{
+ WebMChunkContext *wc = s->priv_data;
+ AVFormatContext *oc = NULL;
+ int ret;
+
+ // DASH Streams can only have either one track per file.
+ if (s->nb_streams != 1) { return AVERROR_INVALIDDATA; }
+
+ wc->chunk_index = wc->chunk_start_index;
+ wc->oformat = av_guess_format("webm", s->filename, "video/webm");
+ if (!wc->oformat)
+ return AVERROR_MUXER_NOT_FOUND;
+
+ ret = chunk_mux_init(s);
+ if (ret < 0)
+ return ret;
+ oc = wc->avf;
+ ret = get_chunk_filename(s, 1, oc->filename);
+ if (ret < 0)
+ return ret;
- avio_close(oc->pb);
++ ret = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL);
+ if (ret < 0)
+ return ret;
+
+ oc->pb->seekable = 0;
+ ret = oc->oformat->write_header(oc);
+ if (ret < 0)
+ return ret;
- ret = ffio_open_whitelist(&pb, filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL, s->protocol_whitelist);
++ ff_format_io_close(s, &oc->pb);
+ return 0;
+}
+
+static int chunk_start(AVFormatContext *s)
+{
+ WebMChunkContext *wc = s->priv_data;
+ AVFormatContext *oc = wc->avf;
+ int ret;
+
+ ret = avio_open_dyn_buf(&oc->pb);
+ if (ret < 0)
+ return ret;
+ wc->chunk_index++;
+ return 0;
+}
+
+static int chunk_end(AVFormatContext *s)
+{
+ WebMChunkContext *wc = s->priv_data;
+ AVFormatContext *oc = wc->avf;
+ int ret;
+ int buffer_size;
+ uint8_t *buffer;
+ AVIOContext *pb;
+ char filename[MAX_FILENAME_SIZE];
+
+ if (wc->chunk_start_index == wc->chunk_index)
+ return 0;
+ // Flush the cluster in WebM muxer.
+ oc->oformat->write_packet(oc, NULL);
+ buffer_size = avio_close_dyn_buf(oc->pb, &buffer);
+ ret = get_chunk_filename(s, 0, filename);
+ if (ret < 0)
+ goto fail;
- ret = avio_close(pb);
- if (ret < 0)
- goto fail;
++ ret = s->io_open(s, &pb, filename, AVIO_FLAG_WRITE, NULL);
+ if (ret < 0)
+ goto fail;
+ avio_write(pb, buffer, buffer_size);
++ ff_format_io_close(s, &pb);
+ oc->pb = NULL;
+fail:
+ av_free(buffer);
+ return (ret < 0) ? ret : 0;
+}
+
+static int webm_chunk_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ WebMChunkContext *wc = s->priv_data;
+ AVFormatContext *oc = wc->avf;
+ AVStream *st = s->streams[pkt->stream_index];
+ int ret;
+
+ if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+ wc->duration_written += av_rescale_q(pkt->pts - wc->prev_pts,
+ st->time_base,
+ (AVRational) {1, 1000});
+ wc->prev_pts = pkt->pts;
+ }
+
+ // For video, a new chunk is started only on key frames. For audio, a new
+ // chunk is started based on chunk_duration.
+ if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
+ (pkt->flags & AV_PKT_FLAG_KEY)) ||
+ (st->codec->codec_type == AVMEDIA_TYPE_AUDIO &&
+ (pkt->pts == 0 || wc->duration_written >= wc->chunk_duration))) {
+ wc->duration_written = 0;
+ if ((ret = chunk_end(s)) < 0 || (ret = chunk_start(s)) < 0) {
+ goto fail;
+ }
+ }
+
+ ret = oc->oformat->write_packet(oc, pkt);
+ if (ret < 0)
+ goto fail;
+
+fail:
+ if (ret < 0) {
+ oc->streams = NULL;
+ oc->nb_streams = 0;
+ avformat_free_context(oc);
+ }
+
+ return ret;
+}
+
+static int webm_chunk_write_trailer(AVFormatContext *s)
+{
+ WebMChunkContext *wc = s->priv_data;
+ AVFormatContext *oc = wc->avf;
+ oc->oformat->write_trailer(oc);
+ chunk_end(s);
+ oc->streams = NULL;
+ oc->nb_streams = 0;
+ avformat_free_context(oc);
+ return 0;
+}
+
+#define OFFSET(x) offsetof(WebMChunkContext, x)
+static const AVOption options[] = {
+ { "chunk_start_index", "start index of the chunk", OFFSET(chunk_start_index), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { "header", "filename of the header where the initialization data will be written", OFFSET(header_filename), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
+ { "audio_chunk_duration", "duration of each chunk in milliseconds", OFFSET(chunk_duration), AV_OPT_TYPE_INT, {.i64 = 5000}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { NULL },
+};
+
+#if CONFIG_WEBM_CHUNK_MUXER
+static const AVClass webm_chunk_class = {
+ .class_name = "WebM Chunk Muxer",
+ .item_name = av_default_item_name,
+ .option = options,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+AVOutputFormat ff_webm_chunk_muxer = {
+ .name = "webm_chunk",
+ .long_name = NULL_IF_CONFIG_SMALL("WebM Chunk Muxer"),
+ .mime_type = "video/webm",
+ .extensions = "chk",
+ .flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_NEEDNUMBER |
+ AVFMT_TS_NONSTRICT,
+ .priv_data_size = sizeof(WebMChunkContext),
+ .write_header = webm_chunk_write_header,
+ .write_packet = webm_chunk_write_packet,
+ .write_trailer = webm_chunk_write_trailer,
+ .priv_class = &webm_chunk_class,
+};
+#endif