[FFmpeg-devel] [PATCH 12/18] avformat/hls: parse ID3 timestamps for elementary audio streams
Anssi Hannula
anssi.hannula at iki.fi
Mon Dec 30 12:14:26 CET 2013
HLS provides MPEG TS timestamps via ID3 tags in the beginning of each
segment of elementary audio streams.
Signed-off-by: Anssi Hannula <anssi.hannula at iki.fi>
---
This is a bit hacky, but I could not see any better way.
libavformat/hls.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 105 insertions(+), 8 deletions(-)
diff --git a/libavformat/hls.c b/libavformat/hls.c
index 85419d9..f4eb1a1 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -36,6 +36,7 @@
#include "internal.h"
#include "avio_internal.h"
#include "url.h"
+#include "id3v2.h"
#define INITIAL_BUFFER_SIZE 32768
@@ -100,6 +101,10 @@ struct playlist {
char key_url[MAX_URL_SIZE];
uint8_t key[16];
+ int is_id3_timestamped; /* -1: not yet known */
+ int64_t id3_mpegts_timestamp; /* in mpegts tb */
+ int64_t id3_offset; /* in stream original tb */
+
/* Renditions associated with this playlist, if any.
* Alternative rendition playlists have a single rendition associated
* with them, and variant main Media Playlists may have
@@ -234,6 +239,10 @@ static struct playlist *new_playlist(HLSContext *c, const char *url,
return NULL;
reset_packet(&pls->pkt);
ff_make_absolute_url(pls->url, sizeof(pls->url), base, url);
+
+ pls->is_id3_timestamped = -1;
+ pls->id3_mpegts_timestamp = AV_NOPTS_VALUE;
+
dynarray_add(&c->playlists, &c->n_playlists, pls);
return pls;
}
@@ -596,6 +605,31 @@ fail:
return ret;
}
+static int64_t get_hls_id3_timestamp(AVIOContext *pb)
+{
+ static const char id3_priv_owner[] = "com.apple.streaming.transportStreamTimestamp";
+ AVDictionary *metadata = NULL;
+ ID3v2ExtraMeta *id3v2_extra_meta = NULL;
+ int64_t hls_timestamp = AV_NOPTS_VALUE;
+ ff_id3v2_read_dict(pb, &metadata, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
+
+ for (ID3v2ExtraMeta *meta = id3v2_extra_meta; meta; meta = meta->next) {
+ if (!strcmp(meta->tag, "PRIV")) {
+ ID3v2ExtraMetaPRIV *priv = meta->data;
+ if (priv->datasize == 8 && !strcmp(priv->owner, id3_priv_owner)) {
+ /* 33-bit MPEG timestamp */
+ hls_timestamp = av_be2ne64(*(uint64_t *)priv->data);
+ break;
+ }
+ }
+ }
+
+ av_dict_free(&metadata);
+ ff_id3v2_free_extra_meta(&id3v2_extra_meta);
+
+ return hls_timestamp;
+}
+
static int open_input(HLSContext *c, struct playlist *pls)
{
AVDictionary *opts = NULL;
@@ -699,6 +733,7 @@ static int read_data(void *opaque, uint8_t *buf, int buf_size)
HLSContext *c = v->parent->priv_data;
int ret, i;
int actual_read_size;
+ int just_opened = 0;
struct segment *seg;
if (!v->needed)
@@ -749,6 +784,7 @@ reload:
v->index);
return ret;
}
+ just_opened = 1;
}
/* limit read if the segment was only a part of a file */
seg = v->segments[v->cur_seq_no - v->start_seq_no];
@@ -758,8 +794,28 @@ reload:
actual_read_size = buf_size;
ret = ffurl_read(v->input, buf, actual_read_size);
- if (ret > 0)
+ if (ret > 0) {
+
+ if (just_opened && v->is_id3_timestamped != 0) {
+ /* Intercept ID3 tags here, elementary audio streams are required
+ * to convey timestamps using them in the beginning of each segment. */
+ AVIOContext id3ioctx;
+ ffio_init_context(&id3ioctx, buf, ret, 0, NULL, NULL, NULL, NULL);
+
+ v->id3_mpegts_timestamp = get_hls_id3_timestamp(&id3ioctx);
+ v->id3_offset = 0;
+ if (v->id3_mpegts_timestamp != AV_NOPTS_VALUE) {
+ int eaten = avio_tell(&id3ioctx);
+ /* drop the id3 tag */
+ ret -= eaten;
+ memmove(buf, buf + eaten, ret);
+ }
+ if (v->is_id3_timestamped == -1)
+ v->is_id3_timestamped = (v->id3_mpegts_timestamp != AV_NOPTS_VALUE);
+ }
+
return ret;
+ }
ffurl_close(v->input);
v->input = NULL;
v->cur_seq_no++;
@@ -981,6 +1037,11 @@ static int hls_read_header(AVFormatContext *s)
if (ret < 0)
goto fail;
+ if (pls->is_id3_timestamped == -1)
+ av_log(s, AV_LOG_WARNING, "No expected HTTP requests have been made\n");
+ else if (pls->is_id3_timestamped == 1 && pls->ctx->nb_streams > 1)
+ av_log(s, AV_LOG_WARNING, "Unexpected ID3 timestamped multi-stream file\n");
+
/* Create new AVStreams for each stream in this playlist */
for (j = 0; j < pls->ctx->nb_streams; j++) {
AVStream *st = avformat_new_stream(s, NULL);
@@ -990,8 +1051,13 @@ static int hls_read_header(AVFormatContext *s)
goto fail;
}
st->id = i;
- avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
+
avcodec_copy_context(st->codec, pls->ctx->streams[j]->codec);
+
+ if (pls->is_id3_timestamped) /* custom timestamps via id3 */
+ avpriv_set_pts_info(st, 33, 1, 90000);
+ else
+ avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
}
add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_AUDIO);
@@ -1078,6 +1144,15 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
return changed;
}
+static AVRational get_timebase(struct playlist *pls, int stream_index)
+{
+ static const AVRational mpeg_tb = {1, 90000};
+ if (pls->is_id3_timestamped)
+ return mpeg_tb;
+
+ return pls->ctx->streams[stream_index]->time_base;
+}
+
static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
{
HLSContext *c = s->priv_data;
@@ -1097,7 +1172,7 @@ start:
if (pls->needed && !pls->pkt.data) {
while (1) {
int64_t ts_diff;
- AVStream *st;
+ AVRational tb;
ret = av_read_frame(pls->ctx, &pls->pkt);
if (ret < 0) {
if (!url_feof(&pls->pb) && ret != AVERROR_EOF)
@@ -1105,10 +1180,30 @@ start:
reset_packet(&pls->pkt);
break;
} else {
+ if (pls->is_id3_timestamped) {
+ /* audio elementary streams are id3 timestamped */
+ if (pls->id3_offset >= 0) {
+ pls->pkt.dts = pls->id3_mpegts_timestamp +
+ av_rescale_q(pls->id3_offset,
+ pls->ctx->streams[pls->pkt.stream_index]->time_base,
+ (AVRational){1, 90000});
+ if (pls->pkt.duration)
+ pls->id3_offset += pls->pkt.duration;
+ else
+ pls->id3_offset = -1;
+ } else {
+ /* there have been packets with unknown duration
+ * since the last id3 tag, should not normally happen */
+ pls->pkt.dts = AV_NOPTS_VALUE;
+ }
+
+ pls->pkt.pts = AV_NOPTS_VALUE;
+ }
+
if (c->first_timestamp == AV_NOPTS_VALUE &&
pls->pkt.dts != AV_NOPTS_VALUE)
c->first_timestamp = av_rescale_q(pls->pkt.dts,
- pls->ctx->streams[pls->pkt.stream_index]->time_base,
+ get_timebase(pls, pls->pkt.stream_index),
AV_TIME_BASE_Q);
}
@@ -1120,9 +1215,9 @@ start:
break;
}
- st = pls->ctx->streams[pls->pkt.stream_index];
+ tb = get_timebase(pls, pls->pkt.stream_index);
ts_diff = av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE,
- st->time_base.den, AV_ROUND_DOWN) -
+ tb.den, AV_ROUND_DOWN) -
c->seek_timestamp;
if (ts_diff >= 0 && (c->seek_flags & AVSEEK_FLAG_ANY ||
pls->pkt.flags & AV_PKT_FLAG_KEY)) {
@@ -1145,6 +1240,8 @@ start:
int64_t mindts = minpls->pkt.dts;
AVStream *st = pls->ctx->streams[pls->pkt.stream_index];
AVStream *minst = minpls->ctx->streams[minpls->pkt.stream_index];
+ AVRational tb = get_timebase( pls, pls->pkt.stream_index);
+ AVRational mintb = get_timebase(minpls, minpls->pkt.stream_index);
if (dts == AV_NOPTS_VALUE) {
minplaylist = i;
@@ -1154,8 +1251,8 @@ start:
if (minst->start_time != AV_NOPTS_VALUE)
mindts -= minst->start_time;
- if (av_compare_ts(dts, st->time_base,
- mindts, minst->time_base) < 0)
+ if (av_compare_ts(dts, tb,
+ mindts, mintb) < 0)
minplaylist = i;
}
}
--
1.8.1.5
More information about the ffmpeg-devel
mailing list