mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-14 05:12:09 +00:00
9ee60482b2
Support reverse playback for ASF format. Parse packets and queue the payloads, then push the payload buffer to decoder in reverse order. Video buffers are pushed from KeyFrame to next Keyframe. https://bugzilla.gnome.org/show_bug.cgi?id=757341
808 lines
26 KiB
C
808 lines
26 KiB
C
/* GStreamer ASF/WMV/WMA demuxer
|
|
* Copyright (C) 2007 Tim-Philipp Müller <tim centricular net>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/* FIXME:
|
|
* file:///home/tpm/samples/video/asf//336370-regis-velo862.wmv
|
|
* file:///home/tpm/samples/video/asf//336370-eichhoer.wmv
|
|
* throw errors (not always necessarily) in this code path
|
|
* (looks like they carry broken payloads/packets though) */
|
|
|
|
#include "asfpacket.h"
|
|
|
|
#include <gst/gstutils.h>
|
|
#include <gst/gstinfo.h>
|
|
#include <string.h>
|
|
|
|
#define GST_ASF_PAYLOAD_KF_COMPLETE(stream, payload) (stream->is_video && payload->keyframe && payload->buf_filled >= payload->mo_size)
|
|
|
|
/* we are unlikely to deal with lengths > 2GB here any time soon, so just
|
|
* return a signed int and use that for error reporting */
|
|
static inline gint
|
|
asf_packet_read_varlen_int (guint lentype_flags, guint lentype_bit_offset,
|
|
const guint8 ** p_data, guint * p_size)
|
|
{
|
|
static const guint lens[4] = { 0, 1, 2, 4 };
|
|
guint len, val;
|
|
|
|
len = lens[(lentype_flags >> lentype_bit_offset) & 0x03];
|
|
|
|
/* will make caller bail out with a short read if there's not enough data */
|
|
if (G_UNLIKELY (*p_size < len)) {
|
|
GST_WARNING ("need %u bytes, but only %u bytes available", len, *p_size);
|
|
return -1;
|
|
}
|
|
|
|
switch (len) {
|
|
case 0:
|
|
val = 0;
|
|
break;
|
|
case 1:
|
|
val = GST_READ_UINT8 (*p_data);
|
|
break;
|
|
case 2:
|
|
val = GST_READ_UINT16_LE (*p_data);
|
|
break;
|
|
case 4:
|
|
val = GST_READ_UINT32_LE (*p_data);
|
|
break;
|
|
default:
|
|
val = 0;
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
*p_data += len;
|
|
*p_size -= len;
|
|
|
|
return (gint) val;
|
|
}
|
|
|
|
static GstBuffer *
|
|
asf_packet_create_payload_buffer (AsfPacket * packet, const guint8 ** p_data,
|
|
guint * p_size, guint payload_len)
|
|
{
|
|
guint off;
|
|
|
|
g_assert (payload_len <= *p_size);
|
|
|
|
off = (guint) (*p_data - packet->bdata);
|
|
g_assert (off < gst_buffer_get_size (packet->buf));
|
|
|
|
*p_data += payload_len;
|
|
*p_size -= payload_len;
|
|
|
|
return gst_buffer_copy_region (packet->buf, GST_BUFFER_COPY_ALL, off,
|
|
payload_len);
|
|
}
|
|
|
|
static AsfPayload *
|
|
asf_payload_search_payloads_queue (AsfPayload * payload, GArray * payload_list)
|
|
{
|
|
AsfPayload *ret = NULL;
|
|
gint idx;
|
|
for (idx = payload_list->len - 1; idx >= 0; idx--) {
|
|
ret = &g_array_index (payload_list, AsfPayload, idx);
|
|
|
|
if (G_UNLIKELY (ret->mo_size == payload->mo_size &&
|
|
ret->mo_number == payload->mo_number)) {
|
|
return ret;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static AsfPayload *
|
|
asf_payload_find_previous_fragment (GstASFDemux * demux, AsfPayload * payload,
|
|
AsfStream * stream)
|
|
{
|
|
AsfPayload *ret = NULL;
|
|
|
|
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
|
|
|
|
/* Search in queued payloads list */
|
|
ret = asf_payload_search_payloads_queue (payload, stream->payloads);
|
|
if (ret) {
|
|
GST_DEBUG
|
|
("previous fragments found in payloads queue for reverse playback : object ID %d",
|
|
ret->mo_number);
|
|
return ret;
|
|
}
|
|
|
|
/* Search in payloads 'to be queued' list */
|
|
ret = asf_payload_search_payloads_queue (payload, stream->payloads_rev);
|
|
if (ret) {
|
|
GST_DEBUG
|
|
("previous fragments found in temp payload queue for reverse playback : object ID %d",
|
|
ret->mo_number);
|
|
return ret;
|
|
}
|
|
} else {
|
|
if (G_UNLIKELY (stream->payloads->len == 0)) {
|
|
GST_DEBUG ("No previous fragments to merge with for stream %u",
|
|
stream->id);
|
|
return NULL;
|
|
}
|
|
|
|
ret =
|
|
&g_array_index (stream->payloads, AsfPayload,
|
|
stream->payloads->len - 1);
|
|
|
|
if (G_UNLIKELY (ret->mo_size != payload->mo_size ||
|
|
ret->mo_number != payload->mo_number || ret->mo_offset != 0)) {
|
|
if (payload->mo_size != 0) {
|
|
GST_WARNING ("Previous fragment does not match continued fragment");
|
|
return NULL;
|
|
} else {
|
|
/* Warn about this case, but accept it anyway: files in the wild sometimes
|
|
* have continued packets where the subsequent fragments say that they're
|
|
* zero-sized. */
|
|
GST_WARNING ("Previous fragment found, but current fragment has "
|
|
"zero size, accepting anyway");
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if (this_fragment->mo_offset + this_payload_len > first_fragment->mo_size) {
|
|
GST_WARNING ("Merged fragments would be bigger than the media object");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* TODO: if we have another payload already queued for this stream and that
|
|
* payload doesn't have a duration, maybe we can calculate a duration for it
|
|
* (if the previous timestamp is smaller etc. etc.) */
|
|
static void
|
|
gst_asf_payload_queue_for_stream_forward (GstASFDemux * demux,
|
|
AsfPayload * payload, AsfStream * stream)
|
|
{
|
|
GST_DEBUG_OBJECT (demux, "Got payload for stream %d ts:%" GST_TIME_FORMAT,
|
|
stream->id, GST_TIME_ARGS (payload->ts));
|
|
|
|
/* make timestamps start from 0; first_ts will be determined during activation (once we have enough data),
|
|
which will also update ts of all packets queued before we knew first_ts; */
|
|
if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (demux->first_ts)
|
|
&& GST_CLOCK_TIME_IS_VALID (payload->ts))) {
|
|
if (payload->ts > demux->first_ts)
|
|
payload->ts -= demux->first_ts;
|
|
else
|
|
payload->ts = 0;
|
|
}
|
|
|
|
/* remove any incomplete payloads that will never be completed */
|
|
while (stream->payloads->len > 0) {
|
|
AsfPayload *prev;
|
|
guint idx_last;
|
|
|
|
idx_last = stream->payloads->len - 1;
|
|
prev = &g_array_index (stream->payloads, AsfPayload, idx_last);
|
|
|
|
if (G_UNLIKELY (gst_asf_payload_is_complete (prev)))
|
|
break;
|
|
|
|
GST_DEBUG_OBJECT (demux, "Dropping incomplete fragmented media object "
|
|
"queued for stream %u", stream->id);
|
|
|
|
gst_buffer_replace (&prev->buf, NULL);
|
|
g_array_remove_index (stream->payloads, idx_last);
|
|
|
|
/* there's data missing, so there's a discontinuity now */
|
|
GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT);
|
|
}
|
|
|
|
/* If we're about to queue a key frame that is before the segment start, we
|
|
* can ditch any previously queued payloads (which would also be before the
|
|
* segment start). This makes sure the decoder doesn't decode more than
|
|
* absolutely necessary after a seek (we don't push out payloads that are
|
|
* before the segment start until we have at least one that falls within the
|
|
* segment) */
|
|
if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (payload->ts) &&
|
|
payload->ts < demux->segment.start && payload->keyframe)) {
|
|
GST_DEBUG_OBJECT (demux, "Queueing keyframe before segment start, removing"
|
|
" %u previously-queued payloads, which would be out of segment too and"
|
|
" hence don't have to be decoded", stream->payloads->len);
|
|
while (stream->payloads->len > 0) {
|
|
AsfPayload *last;
|
|
guint idx_last;
|
|
|
|
idx_last = stream->payloads->len - 1;
|
|
last = &g_array_index (stream->payloads, AsfPayload, idx_last);
|
|
gst_buffer_replace (&last->buf, NULL);
|
|
g_array_remove_index (stream->payloads, idx_last);
|
|
}
|
|
|
|
/* Mark discontinuity (should be done via stream->discont anyway though) */
|
|
GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT);
|
|
}
|
|
|
|
g_array_append_vals (stream->payloads, payload, 1);
|
|
}
|
|
|
|
static void
|
|
gst_asf_payload_queue_for_stream_reverse (GstASFDemux * demux,
|
|
AsfPayload * payload, AsfStream * stream)
|
|
{
|
|
GST_DEBUG_OBJECT (demux, "Got payload for stream %d ts:%" GST_TIME_FORMAT,
|
|
stream->id, GST_TIME_ARGS (payload->ts));
|
|
|
|
if (demux->multiple_payloads) {
|
|
/* store the payload in temporary buffer, until we parse all payloads in this packet */
|
|
g_array_append_vals (stream->payloads_rev, payload, 1);
|
|
} else {
|
|
if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (payload->ts))) {
|
|
g_array_append_vals (stream->payloads, payload, 1);
|
|
if (GST_ASF_PAYLOAD_KF_COMPLETE (stream, payload)) {
|
|
stream->kf_pos = stream->payloads->len - 1;
|
|
}
|
|
} else {
|
|
gst_buffer_unref (payload->buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gst_asf_payload_queue_for_stream (GstASFDemux * demux, AsfPayload * payload,
|
|
AsfStream * stream)
|
|
{
|
|
GST_DEBUG_OBJECT (demux, "Got payload for stream %d ts:%" GST_TIME_FORMAT,
|
|
stream->id, GST_TIME_ARGS (payload->ts));
|
|
|
|
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
|
|
gst_asf_payload_queue_for_stream_reverse (demux, payload, stream);
|
|
} else {
|
|
gst_asf_payload_queue_for_stream_forward (demux, payload, stream);
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
asf_payload_parse_replicated_data_extensions (AsfStream * stream,
|
|
AsfPayload * payload)
|
|
{
|
|
AsfPayloadExtension *ext;
|
|
guint off;
|
|
guint16 ext_len;
|
|
|
|
if (!stream->ext_props.valid || stream->ext_props.payload_extensions == NULL)
|
|
return;
|
|
|
|
off = 8;
|
|
for (ext = stream->ext_props.payload_extensions; ext->len > 0; ++ext) {
|
|
ext_len = ext->len;
|
|
if (ext_len == 0xFFFF) { /* extension length is determined by first two bytes in replicated data */
|
|
ext_len = GST_READ_UINT16_LE (payload->rep_data + off);
|
|
off += 2;
|
|
}
|
|
if (G_UNLIKELY (off + ext_len > payload->rep_data_len)) {
|
|
GST_WARNING ("not enough replicated data for defined extensions");
|
|
return;
|
|
}
|
|
switch (ext->id) {
|
|
case ASF_PAYLOAD_EXTENSION_DURATION:
|
|
if (G_LIKELY (ext_len == 2)) {
|
|
guint16 tdur = GST_READ_UINT16_LE (payload->rep_data + off);
|
|
/* packet durations of 1ms are mostly invalid */
|
|
if (tdur != 1)
|
|
payload->duration = tdur * GST_MSECOND;
|
|
} else {
|
|
GST_WARNING ("unexpected DURATION extensions len %u", ext_len);
|
|
}
|
|
break;
|
|
case ASF_PAYLOAD_EXTENSION_SYSTEM_CONTENT:
|
|
if (G_LIKELY (ext_len == 1)) {
|
|
guint8 data = payload->rep_data[off];
|
|
|
|
payload->interlaced = data & 0x1;
|
|
payload->rff = data & 0x8;
|
|
payload->tff = (data & 0x2) || !(data & 0x4);
|
|
GST_DEBUG ("SYSTEM_CONTENT: interlaced:%d, rff:%d, tff:%d",
|
|
payload->interlaced, payload->rff, payload->tff);
|
|
} else {
|
|
GST_WARNING ("unexpected SYSTEM_CONTE extensions len %u", ext_len);
|
|
}
|
|
break;
|
|
case ASF_PAYLOAD_EXTENSION_SYSTEM_PIXEL_ASPECT_RATIO:
|
|
if (G_LIKELY (ext_len == 2)) {
|
|
payload->par_x = payload->rep_data[off];
|
|
payload->par_y = payload->rep_data[off + 1];
|
|
GST_DEBUG ("PAR %d / %d", payload->par_x, payload->par_y);
|
|
} else {
|
|
GST_WARNING ("unexpected SYSTEM_PIXEL_ASPECT_RATIO extensions len %u",
|
|
ext_len);
|
|
}
|
|
break;
|
|
case ASF_PAYLOAD_EXTENSION_TIMING:
|
|
{
|
|
/* dvr-ms timing - this will override packet timestamp */
|
|
guint64 time = GST_READ_UINT64_LE (payload->rep_data + off + 8);
|
|
if (time != 0xFFFFFFFFFFFFFFFF)
|
|
payload->ts = time * 100;
|
|
else
|
|
payload->ts = GST_CLOCK_TIME_NONE;
|
|
}
|
|
break;
|
|
default:
|
|
GST_LOG ("UNKNOWN PAYLOAD EXTENSION!");
|
|
break;
|
|
}
|
|
off += ext_len;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_asf_demux_parse_payload (GstASFDemux * demux, AsfPacket * packet,
|
|
gint lentype, const guint8 ** p_data, guint * p_size)
|
|
{
|
|
AsfPayload payload = { 0, };
|
|
AsfStream *stream;
|
|
gboolean is_compressed;
|
|
guint payload_len;
|
|
guint stream_num;
|
|
|
|
if (G_UNLIKELY (*p_size < 1)) {
|
|
GST_WARNING_OBJECT (demux, "Short packet!");
|
|
return FALSE;
|
|
}
|
|
|
|
stream_num = GST_READ_UINT8 (*p_data) & 0x7f;
|
|
payload.keyframe = ((GST_READ_UINT8 (*p_data) & 0x80) != 0);
|
|
|
|
*p_data += 1;
|
|
*p_size -= 1;
|
|
|
|
payload.ts = GST_CLOCK_TIME_NONE;
|
|
payload.duration = GST_CLOCK_TIME_NONE;
|
|
payload.par_x = 0;
|
|
payload.par_y = 0;
|
|
payload.interlaced = FALSE;
|
|
payload.tff = FALSE;
|
|
payload.rff = FALSE;
|
|
|
|
payload.mo_number =
|
|
asf_packet_read_varlen_int (packet->prop_flags, 4, p_data, p_size);
|
|
payload.mo_offset =
|
|
asf_packet_read_varlen_int (packet->prop_flags, 2, p_data, p_size);
|
|
payload.rep_data_len =
|
|
asf_packet_read_varlen_int (packet->prop_flags, 0, p_data, p_size);
|
|
|
|
is_compressed = (payload.rep_data_len == 1);
|
|
|
|
GST_LOG_OBJECT (demux, "payload for stream %u", stream_num);
|
|
GST_LOG_OBJECT (demux, "keyframe : %s", (payload.keyframe) ? "yes" : "no");
|
|
GST_LOG_OBJECT (demux, "compressed : %s", (is_compressed) ? "yes" : "no");
|
|
|
|
if (G_UNLIKELY (*p_size < payload.rep_data_len)) {
|
|
GST_WARNING_OBJECT (demux, "Short packet! rep_data_len=%u, size=%u",
|
|
payload.rep_data_len, *p_size);
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy (payload.rep_data, *p_data,
|
|
MIN (sizeof (payload.rep_data), payload.rep_data_len));
|
|
|
|
*p_data += payload.rep_data_len;
|
|
*p_size -= payload.rep_data_len;
|
|
|
|
if (G_UNLIKELY (*p_size == 0)) {
|
|
GST_WARNING_OBJECT (demux, "payload without data!?");
|
|
return FALSE;
|
|
}
|
|
|
|
/* we use -1 as lentype for a single payload that's the size of the packet */
|
|
if (G_UNLIKELY ((lentype >= 0 && lentype <= 3))) {
|
|
payload_len = asf_packet_read_varlen_int (lentype, 0, p_data, p_size);
|
|
if (*p_size < payload_len) {
|
|
GST_WARNING_OBJECT (demux, "Short packet! payload_len=%u, size=%u",
|
|
payload_len, *p_size);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
payload_len = *p_size;
|
|
}
|
|
|
|
GST_LOG_OBJECT (demux, "payload length: %u", payload_len);
|
|
|
|
stream = gst_asf_demux_get_stream (demux, stream_num);
|
|
|
|
if (G_UNLIKELY (stream == NULL)) {
|
|
if (gst_asf_demux_is_unknown_stream (demux, stream_num)) {
|
|
GST_WARNING_OBJECT (demux, "Payload for unknown stream %u, skipping",
|
|
stream_num);
|
|
}
|
|
if (*p_size < payload_len) {
|
|
*p_data += *p_size;
|
|
*p_size = 0;
|
|
} else {
|
|
*p_data += payload_len;
|
|
*p_size -= payload_len;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if (!stream->is_video)
|
|
stream->kf_pos = 0;
|
|
|
|
if (G_UNLIKELY (!is_compressed)) {
|
|
GST_LOG_OBJECT (demux, "replicated data length: %u", payload.rep_data_len);
|
|
|
|
if (payload.rep_data_len >= 8) {
|
|
payload.mo_size = GST_READ_UINT32_LE (payload.rep_data);
|
|
payload.ts = GST_READ_UINT32_LE (payload.rep_data + 4) * GST_MSECOND;
|
|
if (G_UNLIKELY (payload.ts < demux->preroll))
|
|
payload.ts = 0;
|
|
else
|
|
payload.ts -= demux->preroll;
|
|
asf_payload_parse_replicated_data_extensions (stream, &payload);
|
|
|
|
GST_LOG_OBJECT (demux, "media object size : %u", payload.mo_size);
|
|
GST_LOG_OBJECT (demux, "media object ts : %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (payload.ts));
|
|
GST_LOG_OBJECT (demux, "media object dur : %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (payload.duration));
|
|
} else if (payload.rep_data_len == 0) {
|
|
payload.mo_size = 0;
|
|
} else if (payload.rep_data_len != 0) {
|
|
GST_WARNING_OBJECT (demux, "invalid replicated data length, very bad");
|
|
*p_data += payload_len;
|
|
*p_size -= payload_len;
|
|
return FALSE;
|
|
}
|
|
|
|
GST_LOG_OBJECT (demux, "media object offset : %u", payload.mo_offset);
|
|
|
|
GST_LOG_OBJECT (demux, "payload length: %u", payload_len);
|
|
|
|
if (payload_len == 0) {
|
|
GST_DEBUG_OBJECT (demux, "skipping empty payload");
|
|
} else if (payload.mo_offset == 0 && payload.mo_size == payload_len) {
|
|
/* if the media object is not fragmented, just create a sub-buffer */
|
|
GST_LOG_OBJECT (demux, "unfragmented media object size %u", payload_len);
|
|
payload.buf = asf_packet_create_payload_buffer (packet, p_data, p_size,
|
|
payload_len);
|
|
payload.buf_filled = payload_len;
|
|
gst_asf_payload_queue_for_stream (demux, &payload, stream);
|
|
} else if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
|
|
/* Handle fragmented payloads for reverse playback */
|
|
AsfPayload *prev;
|
|
const guint8 *payload_data = *p_data;
|
|
prev = asf_payload_find_previous_fragment (demux, &payload, stream);
|
|
|
|
if (prev) {
|
|
gint idx;
|
|
AsfPayload *p;
|
|
gst_buffer_fill (prev->buf, payload.mo_offset,
|
|
payload_data, payload_len);
|
|
prev->buf_filled += payload_len;
|
|
if (payload.keyframe && payload.mo_offset == 0) {
|
|
stream->reverse_kf_ready = TRUE;
|
|
|
|
for (idx = stream->payloads->len - 1; idx >= 0; idx--) {
|
|
p = &g_array_index (stream->payloads, AsfPayload, idx);
|
|
if (p->mo_number == payload.mo_number) {
|
|
/* Mark position of KF for reverse play */
|
|
stream->kf_pos = idx;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
payload.buf = gst_buffer_new_allocate (NULL, payload.mo_size, NULL); /* can we use (mo_size - offset) for size? */
|
|
gst_buffer_fill (payload.buf, payload.mo_offset,
|
|
payload_data, payload_len);
|
|
payload.buf_filled = payload.mo_size - (payload.mo_offset);
|
|
gst_asf_payload_queue_for_stream (demux, &payload, stream);
|
|
}
|
|
*p_data += payload_len;
|
|
*p_size -= payload_len;
|
|
} else {
|
|
const guint8 *payload_data = *p_data;
|
|
|
|
g_assert (payload_len <= *p_size);
|
|
|
|
*p_data += payload_len;
|
|
*p_size -= payload_len;
|
|
|
|
/* n-th fragment of a fragmented media object? */
|
|
if (payload.mo_offset != 0) {
|
|
AsfPayload *prev;
|
|
|
|
if ((prev =
|
|
asf_payload_find_previous_fragment (demux, &payload, stream))) {
|
|
if (prev->buf == NULL || (payload.mo_size > 0
|
|
&& payload.mo_size != prev->mo_size)
|
|
|| payload.mo_offset >= gst_buffer_get_size (prev->buf)
|
|
|| payload.mo_offset + payload_len >
|
|
gst_buffer_get_size (prev->buf)) {
|
|
GST_WARNING_OBJECT (demux, "Offset doesn't match previous data?!");
|
|
} else {
|
|
/* we assume fragments are payloaded with increasing mo_offset */
|
|
if (payload.mo_offset != prev->buf_filled) {
|
|
GST_WARNING_OBJECT (demux, "media object payload discontinuity: "
|
|
"offset=%u vs buf_filled=%u", payload.mo_offset,
|
|
prev->buf_filled);
|
|
}
|
|
gst_buffer_fill (prev->buf, payload.mo_offset,
|
|
payload_data, payload_len);
|
|
prev->buf_filled =
|
|
MAX (prev->buf_filled, payload.mo_offset + payload_len);
|
|
GST_LOG_OBJECT (demux, "Merged media object fragments, size now %u",
|
|
prev->buf_filled);
|
|
}
|
|
} else {
|
|
GST_DEBUG_OBJECT (demux, "n-th payload fragment, but don't have "
|
|
"any previous fragment, ignoring payload");
|
|
}
|
|
} else {
|
|
GST_LOG_OBJECT (demux, "allocating buffer of size %u for fragmented "
|
|
"media object", payload.mo_size);
|
|
payload.buf = gst_buffer_new_allocate (NULL, payload.mo_size, NULL);
|
|
gst_buffer_fill (payload.buf, 0, payload_data, payload_len);
|
|
payload.buf_filled = payload_len;
|
|
|
|
gst_asf_payload_queue_for_stream (demux, &payload, stream);
|
|
}
|
|
}
|
|
} else {
|
|
const guint8 *payload_data;
|
|
GstClockTime ts, ts_delta;
|
|
guint num;
|
|
|
|
GST_LOG_OBJECT (demux, "Compressed payload, length=%u", payload_len);
|
|
|
|
payload_data = *p_data;
|
|
|
|
*p_data += payload_len;
|
|
*p_size -= payload_len;
|
|
|
|
ts = payload.mo_offset * GST_MSECOND;
|
|
if (G_UNLIKELY (ts < demux->preroll))
|
|
ts = 0;
|
|
else
|
|
ts -= demux->preroll;
|
|
ts_delta = payload.rep_data[0] * GST_MSECOND;
|
|
|
|
for (num = 0; payload_len > 0; ++num) {
|
|
guint sub_payload_len;
|
|
|
|
sub_payload_len = GST_READ_UINT8 (payload_data);
|
|
|
|
GST_LOG_OBJECT (demux, "subpayload #%u: len=%u, ts=%" GST_TIME_FORMAT,
|
|
num, sub_payload_len, GST_TIME_ARGS (ts));
|
|
|
|
++payload_data;
|
|
--payload_len;
|
|
|
|
if (G_UNLIKELY (payload_len < sub_payload_len)) {
|
|
GST_WARNING_OBJECT (demux, "Short payload! %u bytes left", payload_len);
|
|
return FALSE;
|
|
}
|
|
|
|
if (G_LIKELY (sub_payload_len > 0)) {
|
|
payload.buf = asf_packet_create_payload_buffer (packet,
|
|
&payload_data, &payload_len, sub_payload_len);
|
|
payload.buf_filled = sub_payload_len;
|
|
|
|
payload.ts = ts;
|
|
if (G_LIKELY (ts_delta))
|
|
payload.duration = ts_delta;
|
|
else
|
|
payload.duration = GST_CLOCK_TIME_NONE;
|
|
|
|
gst_asf_payload_queue_for_stream (demux, &payload, stream);
|
|
}
|
|
|
|
ts += ts_delta;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GstAsfDemuxParsePacketError
|
|
gst_asf_demux_parse_packet (GstASFDemux * demux, GstBuffer * buf)
|
|
{
|
|
AsfPacket packet = { 0, };
|
|
GstMapInfo map;
|
|
const guint8 *data;
|
|
gboolean has_multiple_payloads;
|
|
GstAsfDemuxParsePacketError ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE;
|
|
guint8 ec_flags, flags1;
|
|
guint size;
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
data = map.data;
|
|
size = map.size;
|
|
GST_LOG_OBJECT (demux, "Buffer size: %u", size);
|
|
|
|
/* need at least two payload flag bytes, send time, and duration */
|
|
if (G_UNLIKELY (size < 2 + 4 + 2)) {
|
|
GST_WARNING_OBJECT (demux, "Packet size is < 8");
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE;
|
|
goto done;
|
|
}
|
|
|
|
packet.buf = buf;
|
|
/* evidently transient */
|
|
packet.bdata = data;
|
|
|
|
ec_flags = GST_READ_UINT8 (data);
|
|
|
|
/* skip optional error correction stuff */
|
|
if ((ec_flags & 0x80) != 0) {
|
|
guint ec_len_type, ec_len;
|
|
|
|
ec_len_type = (ec_flags & 0x60) >> 5;
|
|
if (ec_len_type == 0) {
|
|
ec_len = ec_flags & 0x0f;
|
|
} else {
|
|
GST_WARNING_OBJECT (demux, "unexpected error correction length type %u",
|
|
ec_len_type);
|
|
ec_len = 2;
|
|
}
|
|
GST_LOG_OBJECT (demux, "packet has error correction (%u bytes)", ec_len);
|
|
|
|
/* still need at least two payload flag bytes, send time, and duration */
|
|
if (size <= (1 + ec_len) + 2 + 4 + 2) {
|
|
GST_WARNING_OBJECT (demux, "Packet size is < 8 with Error Correction");
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_FATAL;
|
|
goto done;
|
|
}
|
|
|
|
data += 1 + ec_len;
|
|
size -= 1 + ec_len;
|
|
}
|
|
|
|
/* parse payload info */
|
|
flags1 = GST_READ_UINT8 (data);
|
|
packet.prop_flags = GST_READ_UINT8 (data + 1);
|
|
|
|
data += 2;
|
|
size -= 2;
|
|
|
|
has_multiple_payloads = (flags1 & 0x01) != 0;
|
|
|
|
packet.length = asf_packet_read_varlen_int (flags1, 5, &data, &size);
|
|
|
|
packet.sequence = asf_packet_read_varlen_int (flags1, 1, &data, &size);
|
|
|
|
packet.padding = asf_packet_read_varlen_int (flags1, 3, &data, &size);
|
|
|
|
if (G_UNLIKELY (size < 6)) {
|
|
GST_WARNING_OBJECT (demux, "Packet size is < 6");
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_FATAL;
|
|
goto done;
|
|
}
|
|
|
|
packet.send_time = GST_READ_UINT32_LE (data) * GST_MSECOND;
|
|
packet.duration = GST_READ_UINT16_LE (data + 4) * GST_MSECOND;
|
|
|
|
data += 4 + 2;
|
|
size -= 4 + 2;
|
|
|
|
GST_LOG_OBJECT (demux, "flags : 0x%x", flags1);
|
|
GST_LOG_OBJECT (demux, "multiple payloads: %u", has_multiple_payloads);
|
|
GST_LOG_OBJECT (demux, "packet length : %u", packet.length);
|
|
GST_LOG_OBJECT (demux, "sequence : %u", packet.sequence);
|
|
GST_LOG_OBJECT (demux, "padding : %u", packet.padding);
|
|
GST_LOG_OBJECT (demux, "send time : %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (packet.send_time));
|
|
|
|
GST_LOG_OBJECT (demux, "duration : %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (packet.duration));
|
|
|
|
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)
|
|
&& demux->seek_to_cur_pos == TRUE) {
|
|
/* For reverse playback, initially parse packets forward until we reach packet with 'seek' timestamp */
|
|
if (packet.send_time - demux->preroll > demux->segment.stop) {
|
|
demux->seek_to_cur_pos = FALSE;
|
|
}
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE;
|
|
goto done;
|
|
}
|
|
|
|
if (G_UNLIKELY (packet.padding == (guint) - 1 || size < packet.padding)) {
|
|
GST_WARNING_OBJECT (demux, "No padding, or padding bigger than buffer");
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE;
|
|
goto done;
|
|
}
|
|
|
|
size -= packet.padding;
|
|
|
|
/* adjust available size for parsing if there's less actual packet data for
|
|
* parsing than there is data in bytes (for sample see bug 431318) */
|
|
if (G_UNLIKELY (packet.length != 0 && packet.padding == 0
|
|
&& packet.length < demux->packet_size)) {
|
|
GST_LOG_OBJECT (demux, "shortened packet with implicit padding, "
|
|
"adjusting available data size");
|
|
if (size < demux->packet_size - packet.length) {
|
|
/* the buffer is smaller than the implicit padding */
|
|
GST_WARNING_OBJECT (demux, "Buffer is smaller than the implicit padding");
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE;
|
|
goto done;
|
|
} else {
|
|
/* subtract the implicit padding */
|
|
size -= (demux->packet_size - packet.length);
|
|
}
|
|
}
|
|
|
|
if (has_multiple_payloads) {
|
|
guint i, num, lentype;
|
|
demux->multiple_payloads = TRUE;
|
|
|
|
if (G_UNLIKELY (size < 1)) {
|
|
GST_WARNING_OBJECT (demux, "No room more in buffer");
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE;
|
|
goto done;
|
|
}
|
|
|
|
num = (GST_READ_UINT8 (data) & 0x3F) >> 0;
|
|
lentype = (GST_READ_UINT8 (data) & 0xC0) >> 6;
|
|
|
|
++data;
|
|
--size;
|
|
|
|
GST_LOG_OBJECT (demux, "num payloads : %u", num);
|
|
|
|
for (i = 0; i < num; ++i) {
|
|
GST_LOG_OBJECT (demux, "Parsing payload %u/%u, size left: %u", i + 1, num,
|
|
size);
|
|
|
|
if (G_UNLIKELY (!gst_asf_demux_parse_payload (demux, &packet, lentype,
|
|
&data, &size))) {
|
|
GST_WARNING_OBJECT (demux, "Failed to parse payload %u/%u", i + 1, num);
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_FATAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
|
|
/* In reverse playback, we parsed the packet (with multiple payloads) and stored the payloads in temporary queue.
|
|
Now, add them to the stream's payload queue */
|
|
for (i = 0; i < demux->num_streams; i++) {
|
|
AsfStream *s = &demux->stream[i];
|
|
while (s->payloads_rev->len > 0) {
|
|
AsfPayload *p;
|
|
p = &g_array_index (s->payloads_rev, AsfPayload,
|
|
s->payloads_rev->len - 1);
|
|
g_array_append_vals (s->payloads, p, 1);
|
|
if (GST_ASF_PAYLOAD_KF_COMPLETE (s, p)) {
|
|
/* Mark position of KF for reverse play */
|
|
s->kf_pos = s->payloads->len - 1;
|
|
}
|
|
g_array_remove_index (s->payloads_rev, (s->payloads_rev->len - 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
GST_LOG_OBJECT (demux, "Parsing single payload");
|
|
demux->multiple_payloads = FALSE;
|
|
if (G_UNLIKELY (!gst_asf_demux_parse_payload (demux, &packet, -1, &data,
|
|
&size))) {
|
|
GST_WARNING_OBJECT (demux, "Failed to parse payload");
|
|
ret = GST_ASF_DEMUX_PARSE_PACKET_ERROR_RECOVERABLE;
|
|
}
|
|
}
|
|
|
|
done:
|
|
gst_buffer_unmap (buf, &map);
|
|
return ret;
|
|
}
|