mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-12-07 11:13:41 +02:00
b08569a239
The old one is the result of the reverse engineering and guesswork. The new one has been written following the now-available specification. This work is part of Outreach Program for Women Summer 2014 activities for the Libav project. The fate references had to be changed because the old demuxer truncates the last frame in some cases, the new one handles it properly. The seek-test reference is changed because seeking works differently in the new demuxer. When seeking, the packet is not read from the stream directly, but it is rather constructed by the demuxer. That is why position is -1 now in the reference. Signed-off-by: Anton Khirnov <anton@khirnov.net>
1703 lines
60 KiB
C
1703 lines
60 KiB
C
/*
|
|
* Microsoft Advanced Streaming Format demuxer
|
|
* Copyright (c) 2014 Alexandra Hájková
|
|
*
|
|
* This file is part of Libav.
|
|
*
|
|
* Libav 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.
|
|
*
|
|
* Libav 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 Libav; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "libavutil/attributes.h"
|
|
#include "libavutil/avassert.h"
|
|
#include "libavutil/avstring.h"
|
|
#include "libavutil/bswap.h"
|
|
#include "libavutil/common.h"
|
|
#include "libavutil/dict.h"
|
|
#include "libavutil/internal.h"
|
|
#include "libavutil/mathematics.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/time_internal.h"
|
|
|
|
#include "avformat.h"
|
|
#include "avio_internal.h"
|
|
#include "avlanguage.h"
|
|
#include "id3v2.h"
|
|
#include "internal.h"
|
|
#include "riff.h"
|
|
#include "asf.h"
|
|
#include "asfcrypt.h"
|
|
|
|
#define ASF_BOOL 0x2
|
|
#define ASF_WORD 0x5
|
|
#define ASF_GUID 0x6
|
|
#define ASF_DWORD 0x3
|
|
#define ASF_QWORD 0x4
|
|
#define ASF_UNICODE 0x0
|
|
#define ASF_FLAG_BROADCAST 0x1
|
|
#define ASF_BYTE_ARRAY 0x1
|
|
#define ASF_TYPE_AUDIO 0x2
|
|
#define ASF_TYPE_VIDEO 0x1
|
|
#define ASF_STREAM_NUM 0x7F
|
|
#define ASF_MAX_STREAMS 128
|
|
#define BMP_HEADER_SIZE 40
|
|
#define ASF_NUM_OF_PAYLOADS 0x3F
|
|
#define ASF_ERROR_CORRECTION_LENGTH_TYPE 0x60
|
|
#define ASF_PACKET_ERROR_CORRECTION_DATA_SIZE 0x2
|
|
|
|
typedef struct GUIDParseTable {
|
|
const char *name;
|
|
ff_asf_guid guid;
|
|
int (*read_object)(AVFormatContext *, const struct GUIDParseTable *);
|
|
int is_subobject;
|
|
} GUIDParseTable;
|
|
|
|
typedef struct ASFPacket {
|
|
AVPacket avpkt;
|
|
int64_t dts;
|
|
uint32_t frame_num; // ASF payloads with the same number are parts of the same frame
|
|
int flags;
|
|
int data_size;
|
|
int duration;
|
|
int size_left;
|
|
uint8_t stream_index;
|
|
} ASFPacket;
|
|
|
|
typedef struct ASFStream {
|
|
uint8_t stream_index; // from packet header
|
|
int index; // stream index in AVFormatContext, set in asf_read_stream_properties
|
|
int type;
|
|
int indexed; // added index entries from the Simple Index Object or not
|
|
int8_t span; // for deinterleaving
|
|
uint16_t virtual_pkt_len;
|
|
uint16_t virtual_chunk_len;
|
|
int16_t lang_idx;
|
|
ASFPacket pkt;
|
|
} ASFStream;
|
|
|
|
typedef struct ASFStreamData{
|
|
char langs[32];
|
|
AVDictionary *asf_met; // for storing per-stream metadata
|
|
AVRational aspect_ratio;
|
|
} ASFStreamData;
|
|
|
|
typedef struct ASFContext {
|
|
int data_reached;
|
|
int is_simple_index; // is simple index present or not 1/0
|
|
int is_header;
|
|
|
|
uint64_t preroll;
|
|
uint64_t nb_packets; // ASF packets
|
|
uint32_t packet_size;
|
|
int64_t send_time;
|
|
int duration;
|
|
|
|
uint32_t b_flags; // flags with broadcast flag
|
|
uint32_t prop_flags; // file properties object flags
|
|
|
|
uint64_t data_size; // data object size
|
|
uint64_t unknown_size; // size of the unknown object
|
|
|
|
int64_t offset; // offset of the current object
|
|
|
|
int64_t data_offset;
|
|
int64_t first_packet_offset; // packet offset
|
|
int64_t unknown_offset; // for top level header objects or subobjects without specified behavior
|
|
|
|
// ASF file must not contain more than 128 streams according to the specification
|
|
ASFStream *asf_st[ASF_MAX_STREAMS];
|
|
ASFStreamData asf_sd[ASF_MAX_STREAMS];
|
|
int nb_streams;
|
|
|
|
int stream_index; // from packet header, for the subpayload case
|
|
|
|
// packet parameteres
|
|
uint64_t sub_header_offset; // offset of subplayload header
|
|
int64_t sub_dts;
|
|
uint8_t dts_delta; // for subpayloads
|
|
uint32_t packet_size_internal; // packet size stored inside ASFPacket, can be 0
|
|
int64_t dts;
|
|
int64_t packet_offset; // offset of the current packet inside Data Object
|
|
uint32_t pad_len; // padding after payload
|
|
uint32_t rep_data_len;
|
|
|
|
// packet state
|
|
uint64_t sub_left; // subpayloads left or not
|
|
int nb_sub; // number of subpayloads read so far from the current ASF packet
|
|
uint16_t mult_sub_len; // total length of subpayloads array inside multiple payload
|
|
uint64_t nb_mult_left; // multiple payloads left
|
|
int return_subpayload;
|
|
enum {
|
|
PARSE_PACKET_HEADER,
|
|
READ_SINGLE,
|
|
READ_MULTI,
|
|
READ_MULTI_SUB
|
|
} state;
|
|
} ASFContext;
|
|
|
|
static int detect_unknown_subobject(AVFormatContext *s, int64_t offset, int64_t size);
|
|
static const GUIDParseTable *find_guid(ff_asf_guid guid);
|
|
|
|
static int asf_probe(AVProbeData *pd)
|
|
{
|
|
/* check file header */
|
|
if (!ff_guidcmp(pd->buf, &ff_asf_header))
|
|
return AVPROBE_SCORE_MAX;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void swap_guid(ff_asf_guid guid)
|
|
{
|
|
FFSWAP(unsigned char, guid[0], guid[3]);
|
|
FFSWAP(unsigned char, guid[1], guid[2]);
|
|
FFSWAP(unsigned char, guid[4], guid[5]);
|
|
FFSWAP(unsigned char, guid[6], guid[7]);
|
|
}
|
|
|
|
static void align_position(AVIOContext *pb, int64_t offset, uint64_t size)
|
|
{
|
|
if (avio_tell(pb) != offset + size)
|
|
avio_seek(pb, offset + size, SEEK_SET);
|
|
}
|
|
|
|
static int asf_read_unknown(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t size = avio_rl64(pb);
|
|
int ret;
|
|
|
|
if (asf->is_header)
|
|
asf->unknown_size = size;
|
|
asf->is_header = 0;
|
|
if (!g->is_subobject) {
|
|
if (!(ret = strcmp(g->name, "Header Extension")))
|
|
avio_skip(pb, 22); // skip reserved fields and Data Size
|
|
if ((ret = detect_unknown_subobject(s, asf->unknown_offset,
|
|
asf->unknown_size)) < 0)
|
|
return ret;
|
|
} else
|
|
avio_skip(pb, size - 24);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_asf_string(AVIOContext *pb, int maxlen, char *buf, int buflen)
|
|
{
|
|
char *q = buf;
|
|
int ret = 0;
|
|
if (buflen <= 0)
|
|
return AVERROR(EINVAL);
|
|
while (ret + 1 < maxlen) {
|
|
uint8_t tmp;
|
|
uint32_t ch;
|
|
GET_UTF16(ch, (ret += 2) <= maxlen ? avio_rl16(pb) : 0, break;);
|
|
PUT_UTF8(ch, tmp, if (q - buf < buflen - 1) *q++ = tmp;)
|
|
}
|
|
*q = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int asf_read_marker(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t size = avio_rl64(pb);
|
|
int i, nb_markers, ret;
|
|
size_t len;
|
|
char name[1024];
|
|
|
|
avio_skip(pb, 8);
|
|
avio_skip(pb, 8); // skip reserved GUID
|
|
nb_markers = avio_rl32(pb);
|
|
avio_skip(pb, 2); // skip reserved field
|
|
len = avio_rl16(pb);
|
|
for (i = 0; i < len; i++)
|
|
avio_skip(pb, 1);
|
|
|
|
for (i = 0; i < nb_markers; i++) {
|
|
int64_t pts;
|
|
|
|
avio_skip(pb, 8);
|
|
pts = avio_rl64(pb);
|
|
pts -= asf->preroll * 10000;
|
|
avio_skip(pb, 2); // entry length
|
|
avio_skip(pb, 4); // send time
|
|
avio_skip(pb, 4); // flags
|
|
len = avio_rl32(pb);
|
|
|
|
if ((ret = avio_get_str16le(pb, len, name,
|
|
sizeof(name))) < len)
|
|
avio_skip(pb, len - ret);
|
|
avpriv_new_chapter(s, i, (AVRational) { 1, 10000000 }, pts,
|
|
AV_NOPTS_VALUE, name);
|
|
}
|
|
align_position(pb, asf->offset, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_metadata(AVFormatContext *s, const char *title, uint16_t len,
|
|
unsigned char *ch, uint16_t buflen)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
|
|
avio_get_str16le(pb, len, ch, buflen);
|
|
if (av_dict_set(&s->metadata, title, ch, 0) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_value(AVFormatContext *s, uint8_t *name, uint16_t name_len,
|
|
uint16_t val_len, int type, AVDictionary **met)
|
|
{
|
|
int ret;
|
|
uint8_t *value;
|
|
uint16_t buflen = 2 * val_len + 1;
|
|
AVIOContext *pb = s->pb;
|
|
|
|
value = av_malloc(buflen);
|
|
if (!value)
|
|
return AVERROR(ENOMEM);
|
|
if (type == ASF_UNICODE) {
|
|
// get_asf_string reads UTF-16 and converts it to UTF-8 which needs longer buffer
|
|
if ((ret = get_asf_string(pb, val_len, value, buflen)) < 0)
|
|
goto failed;
|
|
if (av_dict_set(met, name, value, 0) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
} else {
|
|
char buf[256];
|
|
if (val_len > sizeof(buf))
|
|
return AVERROR_INVALIDDATA;
|
|
if ((ret = avio_read(pb, value, val_len)) < 0)
|
|
goto failed;
|
|
if (ret < 2 * val_len)
|
|
value[ret] = '\0';
|
|
else
|
|
value[2 * val_len - 1] = '\0';
|
|
snprintf(buf, sizeof(buf), "%s", value);
|
|
if (av_dict_set(met, name, buf, 0) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
}
|
|
av_freep(&value);
|
|
|
|
return 0;
|
|
|
|
failed:
|
|
av_freep(&value);
|
|
return ret;
|
|
}
|
|
|
|
static int asf_read_generic_value(AVFormatContext *s, uint8_t *name, uint16_t name_len,
|
|
int type, AVDictionary **met)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t value;
|
|
char buf[32];
|
|
|
|
switch (type) {
|
|
case ASF_BOOL:
|
|
value = avio_rl32(pb);
|
|
break;
|
|
case ASF_DWORD:
|
|
value = avio_rl32(pb);
|
|
break;
|
|
case ASF_QWORD:
|
|
value = avio_rl64(pb);
|
|
break;
|
|
case ASF_WORD:
|
|
value = avio_rl16(pb);
|
|
break;
|
|
default:
|
|
av_freep(&name);
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
snprintf(buf, sizeof(buf), "%"PRIu64, value);
|
|
if (av_dict_set(met, name, buf, 0) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* MSDN claims that this should be "compatible with the ID3 frame, APIC",
|
|
* but in reality this is only loosely similar */
|
|
static int asf_read_picture(AVFormatContext *s, int len)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVPacket pkt = { 0 };
|
|
const CodecMime *mime = ff_id3v2_mime_tags;
|
|
enum AVCodecID id = AV_CODEC_ID_NONE;
|
|
char mimetype[64];
|
|
uint8_t *desc = NULL;
|
|
AVStream *st = NULL;
|
|
int ret, type, picsize, desc_len;
|
|
ASFStream *asf_st;
|
|
|
|
/* type + picsize + mime + desc */
|
|
if (len < 1 + 4 + 2 + 2) {
|
|
av_log(s, AV_LOG_ERROR, "Invalid attached picture size: %d.\n", len);
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
|
|
/* picture type */
|
|
type = avio_r8(s->pb);
|
|
len--;
|
|
if (type >= FF_ARRAY_ELEMS(ff_id3v2_picture_types) || type < 0) {
|
|
av_log(s, AV_LOG_WARNING, "Unknown attached picture type: %d.\n", type);
|
|
type = 0;
|
|
}
|
|
|
|
/* picture data size */
|
|
picsize = avio_rl32(s->pb);
|
|
len -= 4;
|
|
|
|
/* picture MIME type */
|
|
len -= avio_get_str16le(s->pb, len, mimetype, sizeof(mimetype));
|
|
while (mime->id != AV_CODEC_ID_NONE) {
|
|
if (!strncmp(mime->str, mimetype, sizeof(mimetype))) {
|
|
id = mime->id;
|
|
break;
|
|
}
|
|
mime++;
|
|
}
|
|
if (id == AV_CODEC_ID_NONE) {
|
|
av_log(s, AV_LOG_ERROR, "Unknown attached picture mimetype: %s.\n",
|
|
mimetype);
|
|
return 0;
|
|
}
|
|
|
|
if (picsize >= len) {
|
|
av_log(s, AV_LOG_ERROR, "Invalid attached picture data size: %d >= %d.\n",
|
|
picsize, len);
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
|
|
/* picture description */
|
|
desc_len = (len - picsize) * 2 + 1;
|
|
desc = av_malloc(desc_len);
|
|
if (!desc)
|
|
return AVERROR(ENOMEM);
|
|
len -= avio_get_str16le(s->pb, len - picsize, desc, desc_len);
|
|
|
|
ret = av_get_packet(s->pb, &pkt, picsize);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
st = avformat_new_stream(s, NULL);
|
|
if (!st) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
asf->asf_st[asf->nb_streams] = av_mallocz(sizeof(*asf_st));
|
|
asf_st = asf->asf_st[asf->nb_streams];
|
|
if (!asf_st)
|
|
return AVERROR(ENOMEM);
|
|
|
|
st->disposition |= AV_DISPOSITION_ATTACHED_PIC;
|
|
st->codec->codec_type = asf_st->type = AVMEDIA_TYPE_VIDEO;
|
|
st->codec->codec_id = id;
|
|
st->attached_pic = pkt;
|
|
st->attached_pic.stream_index = asf_st->index = st->index;
|
|
st->attached_pic.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
asf->nb_streams++;
|
|
|
|
if (*desc) {
|
|
if (av_dict_set(&st->metadata, "title", desc, AV_DICT_DONT_STRDUP_VAL) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
} else
|
|
av_freep(&desc);
|
|
|
|
if (av_dict_set(&st->metadata, "comment", ff_id3v2_picture_types[type], 0) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
av_freep(&desc);
|
|
av_free_packet(&pkt);
|
|
return ret;
|
|
}
|
|
|
|
static void get_id3_tag(AVFormatContext *s, int len)
|
|
{
|
|
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
|
|
|
|
ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
|
|
if (id3v2_extra_meta)
|
|
ff_id3v2_parse_apic(s, &id3v2_extra_meta);
|
|
ff_id3v2_free_extra_meta(&id3v2_extra_meta);
|
|
}
|
|
|
|
static int process_metadata(AVFormatContext *s, uint8_t *name, uint16_t name_len,
|
|
uint16_t val_len, uint16_t type, AVDictionary **met)
|
|
{
|
|
int ret;
|
|
ff_asf_guid guid;
|
|
|
|
if (val_len) {
|
|
switch (type) {
|
|
case ASF_UNICODE:
|
|
asf_read_value(s, name, name_len, val_len, type, met);
|
|
break;
|
|
case ASF_BYTE_ARRAY:
|
|
if (!strcmp(name, "WM/Picture")) // handle cover art
|
|
asf_read_picture(s, val_len);
|
|
else if (!strcmp(name, "ID3")) // handle ID3 tag
|
|
get_id3_tag(s, val_len);
|
|
else
|
|
asf_read_value(s, name, name_len, val_len, type, met);
|
|
break;
|
|
case ASF_GUID:
|
|
ff_get_guid(s->pb, &guid);
|
|
break;
|
|
default:
|
|
if ((ret = asf_read_generic_value(s, name, name_len, type, met)) < 0)
|
|
return ret;
|
|
break;
|
|
}
|
|
}
|
|
av_freep(&name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_ext_content(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t size = avio_rl64(pb);
|
|
uint16_t nb_desc = avio_rl16(pb);
|
|
int i, ret;
|
|
|
|
for (i = 0; i < nb_desc; i++) {
|
|
uint16_t name_len, type, val_len;
|
|
uint8_t *name = NULL;
|
|
|
|
name_len = avio_rl16(pb);
|
|
if (!name_len)
|
|
return AVERROR_INVALIDDATA;
|
|
name = av_malloc(name_len);
|
|
if (!name)
|
|
return AVERROR(ENOMEM);
|
|
avio_get_str16le(pb, name_len, name,
|
|
name_len);
|
|
type = avio_rl16(pb);
|
|
val_len = avio_rl16(pb);
|
|
|
|
if ((ret = process_metadata(s, name, name_len, val_len, type, &s->metadata)) < 0)
|
|
return ret;
|
|
}
|
|
|
|
align_position(pb, asf->offset, size);
|
|
return 0;
|
|
}
|
|
|
|
static AVStream *find_stream(AVFormatContext *s, uint16_t st_num)
|
|
{
|
|
AVStream *st = NULL;
|
|
ASFContext *asf = s->priv_data;
|
|
int i;
|
|
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
if (asf->asf_st[i]->stream_index == st_num) {
|
|
st = s->streams[asf->asf_st[i]->index];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return st;
|
|
}
|
|
|
|
static void asf_store_aspect_ratio(AVFormatContext *s, uint8_t st_num, uint8_t *name)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint16_t value = 0;
|
|
|
|
value = avio_rl16(pb);
|
|
|
|
if (st_num < ASF_MAX_STREAMS) {
|
|
if (!strcmp(name, "AspectRatioX"))
|
|
asf->asf_sd[st_num].aspect_ratio.num = value;
|
|
else
|
|
asf->asf_sd[st_num].aspect_ratio.den = value;
|
|
}
|
|
}
|
|
|
|
static int asf_read_metadata_obj(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t size = avio_rl64(pb);
|
|
uint16_t nb_recs = avio_rl16(pb); // number of records in the Description Records list
|
|
int i, ret;
|
|
|
|
for (i = 0; i < nb_recs; i++) {
|
|
uint16_t name_len, buflen, type, val_len, st_num;
|
|
uint8_t *name = NULL;
|
|
|
|
avio_skip(pb, 2); // skip reserved field
|
|
st_num = avio_rl16(pb);
|
|
name_len = avio_rl16(pb);
|
|
buflen = 2 * name_len + 1;
|
|
if (!name_len)
|
|
break;
|
|
type = avio_rl16(pb);
|
|
val_len = avio_rl32(pb);
|
|
name = av_malloc(name_len);
|
|
if (!name)
|
|
return AVERROR(ENOMEM);
|
|
avio_get_str16le(pb, name_len, name,
|
|
buflen);
|
|
|
|
if (!strcmp(name, "AspectRatioX") || !strcmp(name, "AspectRatioY")) {
|
|
asf_store_aspect_ratio(s, st_num, name);
|
|
} else {
|
|
if (st_num < ASF_MAX_STREAMS) {
|
|
if ((ret = process_metadata(s, name, name_len, val_len, type,
|
|
&asf->asf_sd[st_num].asf_met)) < 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
align_position(pb, asf->offset, size);
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_content_desc(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
int i;
|
|
static const char *const titles[] =
|
|
{ "Title", "Author", "Copyright", "Description", "Rate" };
|
|
uint16_t len[5], buflen[5] = { 0 };
|
|
uint8_t *ch;
|
|
uint64_t size = avio_rl64(pb);
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
len[i] = avio_rl16(pb);
|
|
// utf8 string should be <= 2 * utf16 string, extra byte for the terminator
|
|
buflen[i] = 2 * len[i] + 1;
|
|
}
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
ch = av_malloc(buflen[i]);
|
|
if (!ch)
|
|
return(AVERROR(ENOMEM));
|
|
asf_read_metadata(s, titles[i], len[i], ch, buflen[i]);
|
|
av_freep(&ch);
|
|
}
|
|
align_position(pb, asf->offset, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_properties(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t creation_time;
|
|
|
|
avio_rl64(pb); // read object size
|
|
avio_skip(pb, 16); // skip File ID
|
|
avio_skip(pb, 8); // skip File size
|
|
creation_time = avio_rl64(pb);
|
|
if (!(asf->b_flags & ASF_FLAG_BROADCAST)) {
|
|
struct tm tmbuf;
|
|
struct tm *tm;
|
|
char buf[64];
|
|
|
|
// creation date is in 100 ns units from 1 Jan 1601, conversion to s
|
|
creation_time /= 10000000;
|
|
// there are 11644473600 seconds between 1 Jan 1601 and 1 Jan 1970
|
|
creation_time -= 11644473600;
|
|
tm = gmtime_r(&creation_time, &tmbuf);
|
|
if (tm) {
|
|
if (!strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm))
|
|
buf[0] = '\0';
|
|
} else
|
|
buf[0] = '\0';
|
|
if (buf[0]) {
|
|
if (av_dict_set(&s->metadata, "creation_time", buf, 0) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
}
|
|
}
|
|
asf->nb_packets = avio_rl64(pb);
|
|
asf->duration = avio_rl64(pb) / 10000; // stream duration
|
|
avio_skip(pb, 8); // skip send duration
|
|
asf->preroll = avio_rl64(pb);
|
|
asf->duration -= asf->preroll;
|
|
asf->b_flags = avio_rl32(pb);
|
|
avio_skip(pb, 4); // skip minimal packet size
|
|
asf->packet_size = avio_rl32(pb);
|
|
avio_skip(pb, 4); // skip max_bitrate
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_video_info(AVIOContext *pb, AVStream *st)
|
|
{
|
|
uint16_t size;
|
|
unsigned int tag;
|
|
|
|
st->codec->width = avio_rl32(pb);
|
|
st->codec->height = avio_rl32(pb);
|
|
avio_skip(pb, 1); // skip reserved flags
|
|
size = avio_rl16(pb); // size of the Format Data
|
|
tag = ff_get_bmp_header(pb, st);
|
|
st->codec->codec_tag = tag;
|
|
st->codec->codec_id = ff_codec_get_id(ff_codec_bmp_tags, tag);
|
|
|
|
if (size > BMP_HEADER_SIZE) {
|
|
int ret;
|
|
st->codec->extradata_size = size - BMP_HEADER_SIZE;
|
|
if (!(st->codec->extradata = av_malloc(st->codec->extradata_size +
|
|
FF_INPUT_BUFFER_PADDING_SIZE))) {
|
|
st->codec->extradata_size = 0;
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
memset(st->codec->extradata + st->codec->extradata_size , 0,
|
|
FF_INPUT_BUFFER_PADDING_SIZE);
|
|
if ((ret = avio_read(pb, st->codec->extradata,
|
|
st->codec->extradata_size)) < 0)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_stream_properties(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t size;
|
|
uint32_t err_data_len, ts_data_len; // type specific data length
|
|
uint16_t flags;
|
|
ff_asf_guid stream_type;
|
|
enum AVMediaType type;
|
|
int i, ret;
|
|
uint8_t stream_index;
|
|
AVStream *st;
|
|
ASFStream *asf_st;
|
|
|
|
// ASF file must not contain more than 128 streams according to the specification
|
|
if (asf->nb_streams >= ASF_MAX_STREAMS)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
size = avio_rl64(pb);
|
|
ff_get_guid(pb, &stream_type);
|
|
if (!ff_guidcmp(&stream_type, &ff_asf_audio_stream))
|
|
type = AVMEDIA_TYPE_AUDIO;
|
|
else if (!ff_guidcmp(&stream_type, &ff_asf_video_stream))
|
|
type = AVMEDIA_TYPE_VIDEO;
|
|
else if (!ff_guidcmp(&stream_type, &ff_asf_jfif_media))
|
|
type = AVMEDIA_TYPE_VIDEO;
|
|
else if (!ff_guidcmp(&stream_type, &ff_asf_command_stream))
|
|
type = AVMEDIA_TYPE_DATA;
|
|
else if (!ff_guidcmp(&stream_type,
|
|
&ff_asf_ext_stream_embed_stream_header))
|
|
type = AVMEDIA_TYPE_UNKNOWN;
|
|
else
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
ff_get_guid(pb, &stream_type); // error correction type
|
|
avio_skip(pb, 8); // skip the time offset
|
|
ts_data_len = avio_rl32(pb);
|
|
err_data_len = avio_rl32(pb);
|
|
flags = avio_rl16(pb); // bit 15 - Encrypted Content
|
|
|
|
stream_index = flags & ASF_STREAM_NUM;
|
|
for (i = 0; i < asf->nb_streams; i++)
|
|
if (stream_index == asf->asf_st[i]->stream_index) {
|
|
av_log(s, AV_LOG_WARNING,
|
|
"Duplicate stream found, this stream will be ignored.\n");
|
|
align_position(pb, asf->offset, size);
|
|
return 0;
|
|
}
|
|
|
|
st = avformat_new_stream(s, NULL);
|
|
if (!st)
|
|
return AVERROR(ENOMEM);
|
|
avpriv_set_pts_info(st, 32, 1, 1000); // pts should be dword, in milliseconds
|
|
st->codec->codec_type = type;
|
|
asf->asf_st[asf->nb_streams] = av_mallocz(sizeof(*asf_st));
|
|
if (!asf->asf_st[asf->nb_streams])
|
|
return AVERROR(ENOMEM);
|
|
asf_st = asf->asf_st[asf->nb_streams];
|
|
asf_st->stream_index = stream_index;
|
|
asf_st->index = st->index;
|
|
asf_st->indexed = 0;
|
|
st->id = flags & ASF_STREAM_NUM;
|
|
av_init_packet(&asf_st->pkt.avpkt);
|
|
asf_st->pkt.data_size = 0;
|
|
avio_skip(pb, 4); // skip reserved field
|
|
if (!ts_data_len) {
|
|
av_log(s, AV_LOG_WARNING, "Suspicious data found! ASF stream #%d will be ignored.\n",
|
|
asf_st->stream_index);
|
|
align_position(pb, asf->offset, size);
|
|
return 0;
|
|
}
|
|
|
|
switch (type) {
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
asf_st->type = AVMEDIA_TYPE_AUDIO;
|
|
if ((ret = ff_get_wav_header(pb, st->codec, ts_data_len)) < 0)
|
|
return ret;
|
|
break;
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
asf_st->type = AVMEDIA_TYPE_VIDEO;
|
|
if ((ret = parse_video_info(pb, st)) < 0)
|
|
return ret;
|
|
break;
|
|
default:
|
|
avio_skip(pb, ts_data_len);
|
|
break;
|
|
}
|
|
|
|
if (err_data_len) {
|
|
if (type == AVMEDIA_TYPE_AUDIO) {
|
|
uint8_t span = avio_r8(pb);
|
|
if (span > 1) {
|
|
asf_st->span = span;
|
|
asf_st->virtual_pkt_len = avio_rl16(pb);
|
|
asf_st->virtual_chunk_len = avio_rl16(pb);
|
|
avio_skip(pb, err_data_len - 5);
|
|
} else
|
|
avio_skip(pb, err_data_len - 1);
|
|
} else
|
|
avio_skip(pb, err_data_len);
|
|
}
|
|
|
|
asf->nb_streams++;
|
|
align_position(pb, asf->offset, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_language(AVFormatContext *s, const char *rfc1766, AVDictionary **met)
|
|
{
|
|
// language abbr should contain at least 2 chars
|
|
if (rfc1766 && strlen(rfc1766) > 1) {
|
|
const char primary_tag[3] = { rfc1766[0], rfc1766[1], '\0' }; // ignore country code if any
|
|
const char *iso6392 = av_convert_lang_to(primary_tag,
|
|
AV_LANG_ISO639_2_BIBL);
|
|
if (iso6392)
|
|
if (av_dict_set(met, "language", iso6392, 0) < 0)
|
|
av_log(s, AV_LOG_WARNING, "av_dict_set failed.\n");
|
|
}
|
|
}
|
|
|
|
static int asf_read_ext_stream_properties(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
AVStream *st = NULL;
|
|
ff_asf_guid guid;
|
|
uint16_t nb_st_name, nb_pay_exts, st_num, lang_idx;
|
|
int i, ret;
|
|
uint32_t bitrate;
|
|
uint64_t start_time, end_time, time_per_frame;
|
|
uint64_t size = avio_rl64(pb);
|
|
|
|
start_time = avio_rl64(pb);
|
|
end_time = avio_rl64(pb);
|
|
bitrate = avio_rl32(pb);
|
|
avio_skip(pb, 28); // skip some unused values
|
|
st_num = avio_rl16(pb);
|
|
st_num &= ASF_STREAM_NUM;
|
|
lang_idx = avio_rl16(pb); // Stream Language ID Index
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
if (st_num == asf->asf_st[i]->stream_index) {
|
|
st = s->streams[asf->asf_st[i]->index];
|
|
asf->asf_st[i]->lang_idx = lang_idx;
|
|
break;
|
|
}
|
|
}
|
|
time_per_frame = avio_rl64(pb); // average time per frame
|
|
if (st) {
|
|
st->start_time = start_time;
|
|
st->duration = end_time - start_time;
|
|
st->codec->bit_rate = bitrate;
|
|
st->avg_frame_rate.num = 10000000;
|
|
st->avg_frame_rate.den = time_per_frame;
|
|
}
|
|
nb_st_name = avio_rl16(pb);
|
|
nb_pay_exts = avio_rl16(pb);
|
|
for (i = 0; i < nb_st_name; i++) {
|
|
uint16_t len;
|
|
|
|
avio_rl16(pb); // Language ID Index
|
|
len = avio_rl16(pb);
|
|
avio_skip(pb, len);
|
|
}
|
|
|
|
for (i = 0; i < nb_pay_exts; i++) {
|
|
uint32_t len;
|
|
avio_skip(pb, 16); // Extension System ID
|
|
avio_skip(pb, 2); // Extension Data Size
|
|
len = avio_rl32(pb);
|
|
avio_skip(pb, len);
|
|
}
|
|
|
|
if ((ret = ff_get_guid(pb, &guid)) < 0) {
|
|
align_position(pb, asf->offset, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
g = find_guid(guid);
|
|
if (g && !(strcmp(g->name, "Stream Properties"))) {
|
|
if ((ret = g->read_object(s, g)) < 0)
|
|
return ret;
|
|
}
|
|
|
|
align_position(pb, asf->offset, size);
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_language_list(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
int i, ret;
|
|
uint64_t size = avio_rl64(pb);
|
|
uint16_t nb_langs = avio_rl16(pb);
|
|
|
|
if (nb_langs < ASF_MAX_STREAMS) {
|
|
for (i = 0; i < nb_langs; i++) {
|
|
size_t len;
|
|
len = avio_r8(pb);
|
|
if (!len)
|
|
len = 6;
|
|
if ((ret = get_asf_string(pb, len, asf->asf_sd[i].langs,
|
|
sizeof(asf->asf_sd[i].langs))) < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
align_position(pb, asf->offset, size);
|
|
return 0;
|
|
}
|
|
|
|
// returns data object offset when reading this object for the first time
|
|
static int asf_read_data(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t size = asf->data_size = avio_rl64(pb);
|
|
int i;
|
|
|
|
if (!asf->data_reached && pb->seekable) {
|
|
asf->data_reached = 1;
|
|
asf->data_offset = asf->offset;
|
|
}
|
|
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
if (!(asf->b_flags & ASF_FLAG_BROADCAST))
|
|
s->streams[i]->duration = asf->duration;
|
|
}
|
|
asf->nb_mult_left = 0;
|
|
asf->sub_left = 0;
|
|
asf->state = PARSE_PACKET_HEADER;
|
|
asf->return_subpayload = 0;
|
|
asf->packet_size_internal = 0;
|
|
avio_skip(pb, 16); // skip File ID
|
|
size = avio_rl64(pb); // Total Data Packets
|
|
if (size != asf->nb_packets)
|
|
av_log(s, AV_LOG_WARNING,
|
|
"Number of Packets from File Properties Object is not equal to Total"
|
|
"Datapackets value! num of packets %"PRIu64" total num %"PRIu64".\n",
|
|
size, asf->nb_packets);
|
|
avio_skip(pb, 2); // skip reserved field
|
|
asf->first_packet_offset = avio_tell(pb);
|
|
align_position(pb, asf->offset, asf->data_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_simple_index(AVFormatContext *s, const GUIDParseTable *g)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
AVStream *st = NULL;
|
|
uint64_t interval; // index entry time interval in 100 ns units, usually it's 1s
|
|
uint32_t pkt_num, nb_entries;
|
|
int32_t prev_pkt_num = -1;
|
|
int i;
|
|
uint64_t size = avio_rl64(pb);
|
|
|
|
// simple index objects should be ordered by stream number, this loop tries to find
|
|
// the first not indexed video stream
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
if ((asf->asf_st[i]->type == AVMEDIA_TYPE_VIDEO) && !asf->asf_st[i]->indexed) {
|
|
asf->asf_st[i]->indexed = 1;
|
|
st = s->streams[asf->asf_st[i]->index];
|
|
break;
|
|
}
|
|
}
|
|
if (!st) {
|
|
avio_skip(pb, size - 24); // if there's no video stream, skip index object
|
|
return 0;
|
|
}
|
|
avio_skip(pb, 16); // skip File ID
|
|
interval = avio_rl64(pb);
|
|
avio_skip(pb, 4);
|
|
nb_entries = avio_rl32(pb);
|
|
for (i = 0; i < nb_entries; i++) {
|
|
pkt_num = avio_rl32(pb);
|
|
avio_skip(pb, 2);
|
|
if (prev_pkt_num != pkt_num) {
|
|
av_add_index_entry(st, asf->first_packet_offset + asf->packet_size *
|
|
pkt_num, av_rescale(interval, i, 10000),
|
|
asf->packet_size, 0, AVINDEX_KEYFRAME);
|
|
prev_pkt_num = pkt_num;
|
|
}
|
|
}
|
|
asf->is_simple_index = 1;
|
|
align_position(pb, asf->offset, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const GUIDParseTable gdef[] = {
|
|
{ "Data", { 0x75, 0xB2, 0x26, 0x36, 0x66, 0x8E, 0x11, 0xCF, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }, asf_read_data, 1 },
|
|
{ "Simple Index", { 0x33, 0x00, 0x08, 0x90, 0xE5, 0xB1, 0x11, 0xCF, 0x89, 0xF4, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB }, asf_read_simple_index, 1 },
|
|
{ "Content Description", { 0x75, 0xB2, 0x26, 0x33, 0x66 ,0x8E, 0x11, 0xCF, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }, asf_read_content_desc, 1 },
|
|
{ "Extended Content Description", { 0xD2, 0xD0, 0xA4, 0x40, 0xE3, 0x07, 0x11, 0xD2, 0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5e, 0xA8, 0x50 }, asf_read_ext_content, 1 },
|
|
{ "Stream Bitrate Properties", { 0x7B, 0xF8, 0x75, 0xCE, 0x46, 0x8D, 0x11, 0xD1, 0x8D, 0x82, 0x00, 0x60, 0x97, 0xC9, 0xA2, 0xB2 }, asf_read_unknown, 1 },
|
|
{ "File Properties", { 0x8C, 0xAB, 0xDC, 0xA1, 0xA9, 0x47, 0x11, 0xCF, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }, asf_read_properties, 1 },
|
|
{ "Header Extension", { 0x5F, 0xBF, 0x03, 0xB5, 0xA9, 0x2E, 0x11, 0xCF, 0x8E, 0xE3, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }, asf_read_unknown, 0 },
|
|
{ "Stream Properties", { 0xB7, 0xDC, 0x07, 0x91, 0xA9, 0xB7, 0x11, 0xCF, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }, asf_read_stream_properties, 1 },
|
|
{ "Codec List", { 0x86, 0xD1, 0x52, 0x40, 0x31, 0x1D, 0x11, 0xD0, 0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 }, asf_read_unknown, 1 },
|
|
{ "Marker", { 0xF4, 0x87, 0xCD, 0x01, 0xA9, 0x51, 0x11, 0xCF, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }, asf_read_marker, 1 },
|
|
{ "Script Command", { 0x1E, 0xFB, 0x1A, 0x30, 0x0B, 0x62, 0x11, 0xD0, 0xA3, 0x9B, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 }, asf_read_unknown, 1 },
|
|
{ "Language List", { 0x7C, 0x43, 0x46, 0xa9, 0xef, 0xe0, 0x4B, 0xFC, 0xB2, 0x29, 0x39, 0x3e, 0xde, 0x41, 0x5c, 0x85 }, asf_read_language_list, 1},
|
|
{ "Padding", { 0x18, 0x06, 0xD4, 0x74, 0xCA, 0xDF, 0x45, 0x09, 0xA4, 0xBA, 0x9A, 0xAB, 0xCB, 0x96, 0xAA, 0xE8 }, asf_read_unknown, 1 },
|
|
{ "DRMv1 Header", { 0x22, 0x11, 0xB3, 0xFB, 0xBD, 0x23, 0x11, 0xD2, 0xB4, 0xB7, 0x00, 0xA0, 0xC9, 0x55, 0xFC, 0x6E }, asf_read_unknown, 1 },
|
|
{ "DRMv2 Header", { 0x29, 0x8A, 0xE6, 0x14, 0x26, 0x22, 0x4C, 0x17, 0xB9, 0x35, 0xDA, 0xE0, 0x7E, 0xE9, 0x28, 0x9c }, asf_read_unknown, 1 },
|
|
{ "Index", { 0xD6, 0xE2, 0x29, 0xD3, 0x35, 0xDA, 0x11, 0xD1, 0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE }, asf_read_unknown, 1 },
|
|
{ "Media Object Index", { 0xFE, 0xB1, 0x03, 0xF8, 0x12, 0xAD, 0x4C, 0x64, 0x84, 0x0F, 0x2A, 0x1D, 0x2F, 0x7A, 0xD4, 0x8C }, asf_read_unknown, 1 },
|
|
{ "Timecode Index", { 0x3C, 0xB7, 0x3F, 0xD0, 0x0C, 0x4A, 0x48, 0x03, 0x95, 0x3D, 0xED, 0xF7, 0xB6, 0x22, 0x8F, 0x0C }, asf_read_unknown, 0 },
|
|
{ "Bitrate_Mutual_Exclusion", { 0xD6, 0xE2, 0x29, 0xDC, 0x35, 0xDA, 0x11, 0xD1, 0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE }, asf_read_unknown, 1 },
|
|
{ "Error Correction", { 0x75, 0xB2, 0x26, 0x35, 0x66, 0x8E, 0x11, 0xCF, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }, asf_read_unknown, 1 },
|
|
{ "Content Branding", { 0x22, 0x11, 0xB3, 0xFA, 0xBD, 0x23, 0x11, 0xD2, 0xB4, 0xB7, 0x00, 0xA0, 0xC9, 0x55, 0xFC, 0x6E }, asf_read_unknown, 1 },
|
|
{ "Content Encryption", { 0x22, 0x11, 0xB3, 0xFB, 0xBD, 0x23, 0x11, 0xD2, 0xB4, 0xB7, 0x00, 0xA0, 0xC9, 0x55, 0xFC, 0x6E }, asf_read_unknown, 1 },
|
|
{ "Extended Content Encryption", { 0x29, 0x8A, 0xE6, 0x14, 0x26, 0x22, 0x4C, 0x17, 0xB9, 0x35, 0xDA, 0xE0, 0x7E, 0xE9, 0x28, 0x9C }, asf_read_unknown, 1 },
|
|
{ "Digital Signature", { 0x22, 0x11, 0xB3, 0xFC, 0xBD, 0x23, 0x11, 0xD2, 0xB4, 0xB7, 0x00, 0xA0, 0xC9, 0x55, 0xFC, 0x6E }, asf_read_unknown, 1 },
|
|
{ "Extended Stream Properties", { 0x14, 0xE6, 0xA5, 0xCB, 0xC6, 0x72, 0x43, 0x32, 0x83, 0x99, 0xA9, 0x69, 0x52, 0x06, 0x5B, 0x5A }, asf_read_ext_stream_properties, 1 },
|
|
{ "Advanced Mutual Exclusion", { 0xA0, 0x86, 0x49, 0xCF, 0x47, 0x75, 0x46, 0x70, 0x8A, 0x16, 0x6E, 0x35, 0x35, 0x75, 0x66, 0xCD }, asf_read_unknown, 1 },
|
|
{ "Group Mutual Exclusion", { 0xD1, 0x46, 0x5A, 0x40, 0x5A, 0x79, 0x43, 0x38, 0xB7, 0x1B, 0xE3, 0x6B, 0x8F, 0xD6, 0xC2, 0x49 }, asf_read_unknown, 1},
|
|
{ "Stream Prioritization", { 0xD4, 0xFE, 0xD1, 0x5B, 0x88, 0xD3, 0x45, 0x4F, 0x81, 0xF0, 0xED, 0x5C, 0x45, 0x99, 0x9E, 0x24 }, asf_read_unknown, 1 },
|
|
{ "Bandwidth Sharing Object", { 0xA6, 0x96, 0x09, 0xE6, 0x51, 0x7B, 0x11, 0xD2, 0xB6, 0xAF, 0x00, 0xC0, 0x4F, 0xD9, 0x08, 0xE9 }, asf_read_unknown, 1 },
|
|
{ "Metadata", { 0xC5, 0xF8, 0xCB, 0xEA, 0x5B, 0xAF, 0x48, 0x77, 0x84, 0x67, 0xAA, 0x8C, 0x44, 0xFA, 0x4C, 0xCA }, asf_read_metadata_obj, 1 },
|
|
{ "Metadata Library", { 0x44, 0x23, 0x1C, 0x94, 0x94, 0x98, 0x49, 0xD1, 0xA1, 0x41, 0x1D, 0x13, 0x4E, 0x45, 0x70, 0x54 }, asf_read_metadata_obj, 1 },
|
|
{ "Audio Spread", { 0xBF, 0xC3, 0xCD, 0x50, 0x61, 0x8F, 0x11, 0xCF, 0x8B, 0xB2, 0x00, 0xAA, 0x00, 0xB4, 0xE2, 0x20 }, asf_read_unknown, 1 },
|
|
{ "Index Parameters", { 0xD6, 0xE2, 0x29, 0xDF, 0x35, 0xDA, 0x11, 0xD1, 0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE }, asf_read_unknown, 1 },
|
|
{ "Content Encryption System Windows Media DRM Network Devices",
|
|
{ 0x7A, 0x07, 0x9B, 0xB6, 0xDA, 0XA4, 0x4e, 0x12, 0xA5, 0xCA, 0x91, 0xD3, 0x8D, 0xC1, 0x1A, 0x8D }, asf_read_unknown, 1 },
|
|
{ "Mutex Language", { 0xD6, 0xE2, 0x2A, 0x00, 0x25, 0xDA, 0x11, 0xD1, 0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE }, asf_read_unknown, 1 },
|
|
{ "Mutex Bitrate", { 0xD6, 0xE2, 0x2A, 0x01, 0x25, 0xDA, 0x11, 0xD1, 0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE }, asf_read_unknown, 1 },
|
|
{ "Mutex Unknown", { 0xD6, 0xE2, 0x2A, 0x02, 0x25, 0xDA, 0x11, 0xD1, 0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE }, asf_read_unknown, 1 },
|
|
{ "Bandwith Sharing Exclusive", { 0xAF, 0x60, 0x60, 0xAA, 0x51, 0x97, 0x11, 0xD2, 0xB6, 0xAF, 0x00, 0xC0, 0x4F, 0xD9, 0x08, 0xE9 }, asf_read_unknown, 1 },
|
|
{ "Bandwith Sharing Partial", { 0xAF, 0x60, 0x60, 0xAB, 0x51, 0x97, 0x11, 0xD2, 0xB6, 0xAF, 0x00, 0xC0, 0x4F, 0xD9, 0x08, 0xE9 }, asf_read_unknown, 1 },
|
|
{ "Payload Extension System Timecode", { 0x39, 0x95, 0x95, 0xEC, 0x86, 0x67, 0x4E, 0x2D, 0x8F, 0xDB, 0x98, 0x81, 0x4C, 0xE7, 0x6C, 0x1E }, asf_read_unknown, 1 },
|
|
{ "Payload Extension System File Name", { 0xE1, 0x65, 0xEC, 0x0E, 0x19, 0xED, 0x45, 0xD7, 0xB4, 0xA7, 0x25, 0xCB, 0xD1, 0xE2, 0x8E, 0x9B }, asf_read_unknown, 1 },
|
|
{ "Payload Extension System Content Type", { 0xD5, 0x90, 0xDC, 0x20, 0x07, 0xBC, 0x43, 0x6C, 0x9C, 0xF7, 0xF3, 0xBB, 0xFB, 0xF1, 0xA4, 0xDC }, asf_read_unknown, 1 },
|
|
{ "Payload Extension System Pixel Aspect Ratio", { 0x1, 0x1E, 0xE5, 0x54, 0xF9, 0xEA, 0x4B, 0xC8, 0x82, 0x1A, 0x37, 0x6B, 0x74, 0xE4, 0xC4, 0xB8 }, asf_read_unknown, 1 },
|
|
{ "Payload Extension System Sample Duration", { 0xC6, 0xBD, 0x94, 0x50, 0x86, 0x7F, 0x49, 0x07, 0x83, 0xA3, 0xC7, 0x79, 0x21, 0xB7, 0x33, 0xAD }, asf_read_unknown, 1 },
|
|
{ "Payload Extension System Encryption Sample ID", { 0x66, 0x98, 0xB8, 0x4E, 0x0A, 0xFA, 0x43, 0x30, 0xAE, 0xB2, 0x1C, 0x0A, 0x98, 0xD7, 0xA4, 0x4D }, asf_read_unknown, 1 },
|
|
{ "Payload Extension System Degradable JPEG", { 0x00, 0xE1, 0xAF, 0x06, 0x7B, 0xEC, 0x11, 0xD1, 0xA5, 0x82, 0x00, 0xC0, 0x4F, 0xC2, 0x9C, 0xFB }, asf_read_unknown, 1 },
|
|
};
|
|
|
|
#define READ_LEN(flag, name, len) \
|
|
do { \
|
|
if ((flag) == name ## IS_BYTE) \
|
|
len = avio_r8(pb); \
|
|
else if ((flag) == name ## IS_WORD) \
|
|
len = avio_rl16(pb); \
|
|
else if ((flag) == name ## IS_DWORD) \
|
|
len = avio_rl32(pb); \
|
|
else \
|
|
len = 0; \
|
|
} while(0)
|
|
|
|
static int asf_read_subpayload(AVFormatContext *s, AVPacket *pkt, int is_header)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint8_t sub_len;
|
|
int ret, i;
|
|
|
|
if (is_header) {
|
|
asf->dts_delta = avio_r8(pb);
|
|
if (asf->nb_mult_left) {
|
|
asf->mult_sub_len = avio_rl16(pb); // total
|
|
}
|
|
asf->sub_header_offset = avio_tell(pb);
|
|
asf->nb_sub = 0;
|
|
asf->sub_left = 1;
|
|
}
|
|
sub_len = avio_r8(pb);
|
|
if ((ret = av_get_packet(pb, pkt, sub_len)) < 0) // each subpayload is entire frame
|
|
return ret;
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
if (asf->stream_index == asf->asf_st[i]->stream_index) {
|
|
pkt->stream_index = asf->asf_st[i]->index;
|
|
break;
|
|
}
|
|
}
|
|
asf->return_subpayload = 1;
|
|
if (!sub_len)
|
|
asf->return_subpayload = 0;
|
|
|
|
if (sub_len)
|
|
asf->nb_sub++;
|
|
pkt->dts = asf->sub_dts + (asf->nb_sub - 1) * asf->dts_delta - asf->preroll;
|
|
if (asf->nb_mult_left && (avio_tell(pb) >=
|
|
(asf->sub_header_offset + asf->mult_sub_len))) {
|
|
asf->sub_left = 0;
|
|
asf->nb_mult_left--;
|
|
}
|
|
if (avio_tell(pb) >= asf->packet_offset + asf->packet_size - asf->pad_len) {
|
|
asf->sub_left = 0;
|
|
if (!asf->nb_mult_left) {
|
|
avio_skip(pb, asf->pad_len);
|
|
if (avio_tell(pb) != asf->packet_offset + asf->packet_size) {
|
|
if (!asf->packet_size)
|
|
return AVERROR_INVALIDDATA;
|
|
av_log(s, AV_LOG_WARNING,
|
|
"Position %"PRId64" wrong, should be %"PRId64"\n",
|
|
avio_tell(pb), asf->packet_offset + asf->packet_size);
|
|
avio_seek(pb, asf->packet_offset + asf->packet_size, SEEK_SET);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void reset_packet(ASFPacket *asf_pkt)
|
|
{
|
|
asf_pkt->size_left = 0;
|
|
asf_pkt->data_size = 0;
|
|
asf_pkt->duration = 0;
|
|
asf_pkt->flags = 0;
|
|
asf_pkt->dts = 0;
|
|
asf_pkt->duration = 0;
|
|
av_free_packet(&asf_pkt->avpkt);
|
|
av_init_packet(&asf_pkt->avpkt);
|
|
}
|
|
|
|
static int asf_read_multiple_payload(AVFormatContext *s, AVPacket *pkt,
|
|
ASFPacket *asf_pkt)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint16_t pay_len;
|
|
unsigned char *p;
|
|
int ret;
|
|
int skip = 0;
|
|
|
|
// if replicated lenght is 1, subpayloads are present
|
|
if (asf->rep_data_len == 1) {
|
|
asf->sub_left = 1;
|
|
asf->state = READ_MULTI_SUB;
|
|
pkt->flags = asf_pkt->flags;
|
|
if ((ret = asf_read_subpayload(s, pkt, 1)) < 0)
|
|
return ret;
|
|
} else {
|
|
if (!asf_pkt->data_size) {
|
|
asf_pkt->data_size = asf_pkt->size_left = avio_rl32(pb); // read media object size
|
|
if (asf_pkt->data_size <= 0)
|
|
return AVERROR_EOF;
|
|
if ((ret = av_new_packet(&asf_pkt->avpkt, asf_pkt->data_size)) < 0)
|
|
return ret;
|
|
} else
|
|
avio_skip(pb, 4); // reading of media object size is already done
|
|
asf_pkt->dts = avio_rl32(pb); // read presentation time
|
|
if ((asf->rep_data_len - 8) > 0)
|
|
avio_skip(pb, asf->rep_data_len - 8); // skip replicated data
|
|
pay_len = avio_rl16(pb); // payload length should be WORD
|
|
if (pay_len > asf->packet_size) {
|
|
av_log(s, AV_LOG_ERROR,
|
|
"Error: invalid data packet size, pay_len %"PRIu16", "
|
|
"asf->packet_size %"PRIu32", offset %"PRId64".\n",
|
|
pay_len, asf->packet_size, avio_tell(pb));
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
p = asf_pkt->avpkt.data + asf_pkt->data_size - asf_pkt->size_left;
|
|
if (pay_len > asf_pkt->size_left) {
|
|
av_log(s, AV_LOG_ERROR,
|
|
"Error: invalid buffer size, pay_len %d, data size left %d.\n",
|
|
pay_len, asf_pkt->size_left);
|
|
skip = pay_len - asf_pkt->size_left;
|
|
pay_len = asf_pkt->size_left;
|
|
}
|
|
if ((ret = avio_read(pb, p, pay_len)) < 0)
|
|
return ret;
|
|
if (s->key && s->keylen == 20)
|
|
ff_asfcrypt_dec(s->key, p, ret);
|
|
avio_skip(pb, skip);
|
|
asf_pkt->size_left -= pay_len;
|
|
asf->nb_mult_left--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_single_payload(AVFormatContext *s, AVPacket *pkt,
|
|
ASFPacket *asf_pkt)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
int64_t offset;
|
|
uint64_t size;
|
|
unsigned char *p;
|
|
int ret;
|
|
|
|
if (!asf_pkt->data_size) {
|
|
asf_pkt->data_size = asf_pkt->size_left = avio_rl32(pb); // read media object size
|
|
if (asf_pkt->data_size <= 0)
|
|
return AVERROR_EOF;
|
|
if ((ret = av_new_packet(&asf_pkt->avpkt, asf_pkt->data_size)) < 0)
|
|
return ret;
|
|
} else
|
|
avio_skip(pb, 4); // skip media object size
|
|
asf_pkt->dts = avio_rl32(pb); // read presentation time
|
|
if ((asf->rep_data_len - 8) > 0)
|
|
avio_skip(pb, asf->rep_data_len - 8); // skip replicated data
|
|
offset = avio_tell(pb);
|
|
|
|
// size of the payload - size of the packet without header and padding
|
|
if (asf->packet_size_internal)
|
|
size = asf->packet_size_internal - offset + asf->packet_offset - asf->pad_len;
|
|
else
|
|
size = asf->packet_size - offset + asf->packet_offset - asf->pad_len;
|
|
if (size > asf->packet_size) {
|
|
av_log(s, AV_LOG_ERROR,
|
|
"Error: invalid data packet size, offset %"PRId64".\n",
|
|
avio_tell(pb));
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
p = asf_pkt->avpkt.data + asf_pkt->data_size - asf_pkt->size_left;
|
|
if (size > asf_pkt->size_left)
|
|
return AVERROR_INVALIDDATA;
|
|
if (asf_pkt->size_left > size)
|
|
asf_pkt->size_left -= size;
|
|
else
|
|
asf_pkt->size_left = 0;
|
|
if ((ret = avio_read(pb, p, size)) < 0)
|
|
return ret;
|
|
if (s->key && s->keylen == 20)
|
|
ff_asfcrypt_dec(s->key, p, ret);
|
|
if (asf->packet_size_internal)
|
|
avio_skip(pb, asf->packet_size - asf->packet_size_internal);
|
|
avio_skip(pb, asf->pad_len); // skip padding
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_payload(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
int ret, i;
|
|
ASFPacket *asf_pkt = NULL;
|
|
|
|
if (!asf->sub_left) {
|
|
uint32_t off_len, media_len;
|
|
uint8_t stream_num;
|
|
|
|
stream_num = avio_r8(pb);
|
|
asf->stream_index = stream_num & ASF_STREAM_NUM;
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
if (asf->stream_index == asf->asf_st[i]->stream_index) {
|
|
asf_pkt = &asf->asf_st[i]->pkt;
|
|
asf_pkt->stream_index = asf->asf_st[i]->index;
|
|
asf_pkt->dts = asf->dts;
|
|
break;
|
|
}
|
|
}
|
|
if (!asf_pkt)
|
|
return AVERROR_INVALIDDATA;
|
|
if (stream_num >> 7)
|
|
asf_pkt->flags |= AV_PKT_FLAG_KEY;
|
|
READ_LEN(asf->prop_flags & ASF_PL_MASK_MEDIA_OBJECT_NUMBER_LENGTH_FIELD_SIZE,
|
|
ASF_PL_FLAG_MEDIA_OBJECT_NUMBER_LENGTH_FIELD_, media_len);
|
|
READ_LEN(asf->prop_flags & ASF_PL_MASK_OFFSET_INTO_MEDIA_OBJECT_LENGTH_FIELD_SIZE,
|
|
ASF_PL_FLAG_OFFSET_INTO_MEDIA_OBJECT_LENGTH_FIELD_, off_len);
|
|
READ_LEN(asf->prop_flags & ASF_PL_MASK_REPLICATED_DATA_LENGTH_FIELD_SIZE,
|
|
ASF_PL_FLAG_REPLICATED_DATA_LENGTH_FIELD_, asf->rep_data_len);
|
|
if (asf_pkt->size_left && (asf_pkt->frame_num != media_len)) {
|
|
av_log(s, AV_LOG_WARNING, "Unfinished frame will be ignored\n");
|
|
reset_packet(asf_pkt);
|
|
}
|
|
asf_pkt->frame_num = media_len;
|
|
asf->sub_dts = off_len;
|
|
if (asf->nb_mult_left) {
|
|
if ((ret = asf_read_multiple_payload(s, pkt, asf_pkt)) < 0)
|
|
return ret;
|
|
} else if (asf->rep_data_len == 1) {
|
|
asf->sub_left = 1;
|
|
asf->state = READ_SINGLE;
|
|
pkt->flags = asf_pkt->flags;
|
|
if ((ret = asf_read_subpayload(s, pkt, 1)) < 0)
|
|
return ret;
|
|
} else {
|
|
if ((ret = asf_read_single_payload(s, pkt, asf_pkt)) < 0)
|
|
return ret;
|
|
}
|
|
} else {
|
|
for (i = 0; i <= asf->nb_streams; i++) {
|
|
if (asf->stream_index == asf->asf_st[i]->stream_index) {
|
|
asf_pkt = &asf->asf_st[i]->pkt;
|
|
break;
|
|
}
|
|
}
|
|
if (!asf_pkt)
|
|
return AVERROR_INVALIDDATA;
|
|
pkt->flags = asf_pkt->flags;
|
|
pkt->dts = asf_pkt->dts;
|
|
pkt->stream_index = asf->asf_st[i]->index;
|
|
if ((ret = asf_read_subpayload(s, pkt, 0)) < 0) // read subpayload without its header
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_packet_header(AVFormatContext *s)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
uint64_t size;
|
|
uint32_t av_unused seq;
|
|
unsigned char error_flags, len_flags, pay_flags;
|
|
|
|
asf->packet_offset = avio_tell(pb);
|
|
error_flags = avio_r8(pb); // read Error Correction Flags
|
|
if (error_flags & ASF_PACKET_FLAG_ERROR_CORRECTION_PRESENT)
|
|
if (!(error_flags & ASF_ERROR_CORRECTION_LENGTH_TYPE)) {
|
|
size = error_flags & ASF_PACKET_ERROR_CORRECTION_DATA_SIZE;
|
|
avio_skip(pb, size);
|
|
}
|
|
len_flags = avio_r8(pb);
|
|
asf->prop_flags = avio_r8(pb);
|
|
READ_LEN(len_flags & ASF_PPI_MASK_PACKET_LENGTH_FIELD_SIZE,
|
|
ASF_PPI_FLAG_PACKET_LENGTH_FIELD_, asf->packet_size_internal);
|
|
READ_LEN(len_flags & ASF_PPI_MASK_SEQUENCE_FIELD_SIZE,
|
|
ASF_PPI_FLAG_SEQUENCE_FIELD_, seq);
|
|
READ_LEN(len_flags & ASF_PPI_MASK_PADDING_LENGTH_FIELD_SIZE,
|
|
ASF_PPI_FLAG_PADDING_LENGTH_FIELD_, asf->pad_len );
|
|
asf->send_time = avio_rl32(pb); // send time
|
|
avio_skip(pb, 2); // skip duration
|
|
if (len_flags & ASF_PPI_FLAG_MULTIPLE_PAYLOADS_PRESENT) { // Multiple Payloads present
|
|
pay_flags = avio_r8(pb);
|
|
asf->nb_mult_left = (pay_flags & ASF_NUM_OF_PAYLOADS);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_deinterleave(AVFormatContext *s, ASFPacket *asf_pkt, int st_num)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
ASFStream *asf_st = asf->asf_st[st_num];
|
|
unsigned char *p = asf_pkt->avpkt.data;
|
|
uint16_t pkt_len = asf->asf_st[st_num]->virtual_pkt_len;
|
|
uint16_t chunk_len = asf->asf_st[st_num]->virtual_chunk_len;
|
|
int nchunks = pkt_len / chunk_len;
|
|
AVPacket pkt;
|
|
int pos = 0, j, l, ret;
|
|
|
|
|
|
if ((ret = av_new_packet(&pkt, asf_pkt->data_size)) < 0)
|
|
return ret;
|
|
|
|
while (asf_pkt->data_size >= asf_st->span * pkt_len + pos) {
|
|
if (pos >= asf_pkt->data_size) {
|
|
break;
|
|
}
|
|
for (l = 0; l < pkt_len; l++) {
|
|
if (pos >= asf_pkt->data_size) {
|
|
break;
|
|
}
|
|
for (j = 0; j < asf_st->span; j++) {
|
|
if ((pos + chunk_len) >= asf_pkt->data_size)
|
|
break;
|
|
memcpy(pkt.data + pos,
|
|
p + (j * nchunks + l) * chunk_len,
|
|
chunk_len);
|
|
pos += chunk_len;
|
|
}
|
|
}
|
|
p += asf_st->span * pkt_len;
|
|
if (p > asf_pkt->avpkt.data + asf_pkt->data_size)
|
|
break;
|
|
}
|
|
av_free_packet(&asf_pkt->avpkt);
|
|
asf_pkt->avpkt = pkt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_packet(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
int ret, i;
|
|
|
|
if ((avio_tell(pb) >= asf->data_offset + asf->data_size) &&
|
|
!(asf->b_flags & ASF_FLAG_BROADCAST))
|
|
return AVERROR_EOF;
|
|
while (!pb->eof_reached) {
|
|
if (asf->state == PARSE_PACKET_HEADER) {
|
|
asf_read_packet_header(s);
|
|
if (!asf->nb_mult_left)
|
|
asf->state = READ_SINGLE;
|
|
else
|
|
asf->state = READ_MULTI;
|
|
}
|
|
if ((ret = asf_read_payload(s, pkt)) < 0)
|
|
return ret;
|
|
switch (asf->state) {
|
|
case READ_SINGLE:
|
|
if (!asf->sub_left)
|
|
asf->state = PARSE_PACKET_HEADER;
|
|
break;
|
|
case READ_MULTI_SUB:
|
|
if (!asf->sub_left && !asf->nb_mult_left) {
|
|
asf->state = PARSE_PACKET_HEADER;
|
|
if (!asf->return_subpayload)
|
|
avio_skip(pb, asf->pad_len); // skip padding
|
|
if (asf->packet_offset + asf->packet_size > avio_tell(pb))
|
|
avio_seek(pb, asf->packet_offset + asf->packet_size, SEEK_SET);
|
|
} else if (!asf->sub_left)
|
|
asf->state = READ_MULTI;
|
|
break;
|
|
case READ_MULTI:
|
|
if (!asf->nb_mult_left) {
|
|
asf->state = PARSE_PACKET_HEADER;
|
|
if (!asf->return_subpayload) {
|
|
avio_skip(pb, asf->pad_len); // skip padding
|
|
}
|
|
if (asf->packet_offset + asf->packet_size > avio_tell(pb))
|
|
avio_seek(pb, asf->packet_offset + asf->packet_size, SEEK_SET);
|
|
}
|
|
break;
|
|
}
|
|
if (asf->return_subpayload) {
|
|
asf->return_subpayload = 0;
|
|
return 0;
|
|
}
|
|
for (i = 0; i < s->nb_streams; i++) {
|
|
ASFPacket *asf_pkt = &asf->asf_st[i]->pkt;
|
|
if (asf_pkt && !asf_pkt->size_left && asf_pkt->data_size) {
|
|
if (asf->asf_st[i]->span > 1 &&
|
|
asf->asf_st[i]->type == AVMEDIA_TYPE_AUDIO)
|
|
if ((ret = asf_deinterleave(s, asf_pkt, i)) < 0)
|
|
return ret;
|
|
av_packet_move_ref(pkt, &asf_pkt->avpkt);
|
|
pkt->stream_index = asf->asf_st[i]->index;
|
|
pkt->flags = asf_pkt->flags;
|
|
pkt->dts = asf_pkt->dts - asf->preroll;
|
|
asf_pkt->data_size = 0;
|
|
asf_pkt->frame_num = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pb->eof_reached)
|
|
return AVERROR_EOF;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_close(AVFormatContext *s)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
int i;
|
|
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
av_free_packet(&asf->asf_st[i]->pkt.avpkt);
|
|
av_freep(&asf->asf_st[i]);
|
|
av_dict_free(&asf->asf_sd[i].asf_met);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void reset_packet_state(AVFormatContext *s)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
int i;
|
|
|
|
asf->state = PARSE_PACKET_HEADER;
|
|
asf->offset = 0;
|
|
asf->return_subpayload = 0;
|
|
asf->sub_left = 0;
|
|
asf->sub_header_offset = 0;
|
|
asf->packet_offset = asf->first_packet_offset;
|
|
asf->pad_len = 0;
|
|
asf->rep_data_len = 0;
|
|
asf->dts_delta = 0;
|
|
asf->mult_sub_len = 0;
|
|
asf->nb_mult_left = 0;
|
|
asf->nb_sub = 0;
|
|
asf->prop_flags = 0;
|
|
asf->sub_dts = 0;
|
|
asf->dts = 0;
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
ASFPacket *pkt = &asf->asf_st[i]->pkt;
|
|
pkt->size_left = 0;
|
|
pkt->data_size = 0;
|
|
pkt->duration = 0;
|
|
pkt->flags = 0;
|
|
pkt->dts = 0;
|
|
pkt->duration = 0;
|
|
av_free_packet(&pkt->avpkt);
|
|
av_init_packet(&pkt->avpkt);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find a timestamp for the requested position within the payload
|
|
* where the pos (position) is the offset inside the Data Object.
|
|
* When position is not on the packet boundary, asf_read_timestamp tries
|
|
* to find the closest packet offset after this position. If this packet
|
|
* is a key frame, this packet timestamp is read and an index entry is created
|
|
* for the packet. If this packet belongs to the requested stream,
|
|
* asf_read_timestamp upgrades pos to the packet beginning offset and
|
|
* returns this packet's dts. So returned dts is the dts of the first key frame with
|
|
* matching stream number after given position.
|
|
*/
|
|
static int64_t asf_read_timestamp(AVFormatContext *s, int stream_index,
|
|
int64_t *pos, int64_t pos_limit)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
int64_t pkt_pos = *pos, pkt_offset, dts = AV_NOPTS_VALUE, data_end;
|
|
AVPacket pkt;
|
|
int n;
|
|
|
|
data_end = asf->data_offset + asf->data_size;
|
|
|
|
n = (pkt_pos - asf->first_packet_offset + asf->packet_size - 1) /
|
|
asf->packet_size;
|
|
n = av_clip(n, 0, ((data_end - asf->first_packet_offset) / asf->packet_size - 1));
|
|
pkt_pos = asf->first_packet_offset + n * asf->packet_size;
|
|
|
|
avio_seek(s->pb, pkt_pos, SEEK_SET);
|
|
pkt_offset = pkt_pos;
|
|
|
|
reset_packet_state(s);
|
|
while (avio_tell(s->pb) < data_end) {
|
|
|
|
int i, ret, st_found;
|
|
|
|
av_init_packet(&pkt);
|
|
pkt_offset = avio_tell(s->pb);
|
|
if ((ret = asf_read_packet(s, &pkt)) < 0) {
|
|
dts = AV_NOPTS_VALUE;
|
|
return ret;
|
|
}
|
|
// ASFPacket may contain fragments of packets belonging to different streams,
|
|
// pkt_offset is the offset of the first fragment within it.
|
|
if ((pkt_offset >= (pkt_pos + asf->packet_size)))
|
|
pkt_pos += asf->packet_size;
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
ASFStream *st = asf->asf_st[i];
|
|
|
|
st_found = 0;
|
|
if (pkt.flags & AV_PKT_FLAG_KEY) {
|
|
dts = pkt.dts;
|
|
if (dts) {
|
|
av_add_index_entry(s->streams[pkt.stream_index], pkt_pos,
|
|
dts, pkt.size, 0, AVINDEX_KEYFRAME);
|
|
if (stream_index == st->index) {
|
|
st_found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (st_found)
|
|
break;
|
|
av_free_packet(&pkt);
|
|
}
|
|
*pos = pkt_pos;
|
|
|
|
av_free_packet(&pkt);
|
|
return dts;
|
|
}
|
|
|
|
static int asf_read_seek(AVFormatContext *s, int stream_index,
|
|
int64_t timestamp, int flags)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
int idx, ret;
|
|
|
|
if (s->streams[stream_index]->nb_index_entries && asf->is_simple_index) {
|
|
idx = av_index_search_timestamp(s->streams[stream_index], timestamp, flags);
|
|
if (idx < 0 || idx >= s->streams[stream_index]->nb_index_entries)
|
|
return AVERROR_INVALIDDATA;
|
|
avio_seek(s->pb, s->streams[stream_index]->index_entries[idx].pos, SEEK_SET);
|
|
} else {
|
|
if ((ret = ff_seek_frame_binary(s, stream_index, timestamp, flags)) < 0)
|
|
return ret;
|
|
|
|
// asf_read_timestamp is called inside ff_seek_frame_binary and leaves state dirty,
|
|
// so reset_packet_state have to be called after it.
|
|
reset_packet_state(s);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const GUIDParseTable *find_guid(ff_asf_guid guid)
|
|
{
|
|
int j, ret;
|
|
const GUIDParseTable *g;
|
|
|
|
swap_guid(guid);
|
|
g = gdef;
|
|
for (j = 0; j < FF_ARRAY_ELEMS(gdef); j++) {
|
|
if (!(ret = memcmp(guid, g->guid, sizeof(g->guid))))
|
|
return g;
|
|
g++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int detect_unknown_subobject(AVFormatContext *s, int64_t offset, int64_t size)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
const GUIDParseTable *g = NULL;
|
|
ff_asf_guid guid;
|
|
int ret;
|
|
|
|
while (avio_tell(pb) <= offset + size) {
|
|
asf->offset = avio_tell(pb);
|
|
if ((ret = ff_get_guid(pb, &guid)) < 0)
|
|
return ret;
|
|
g = find_guid(guid);
|
|
if (g) {
|
|
if ((ret = g->read_object(s, g)) < 0)
|
|
return ret;
|
|
} else {
|
|
GUIDParseTable g2;
|
|
|
|
g2.name = "Unknown";
|
|
g2.is_subobject = 1;
|
|
asf_read_unknown(s, &g2);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asf_read_header(AVFormatContext *s)
|
|
{
|
|
ASFContext *asf = s->priv_data;
|
|
AVIOContext *pb = s->pb;
|
|
const GUIDParseTable *g = NULL;
|
|
ff_asf_guid guid;
|
|
int i, ret;
|
|
uint64_t size;
|
|
|
|
asf->preroll = 0;
|
|
asf->is_simple_index = 0;
|
|
ff_get_guid(pb, &guid);
|
|
if (ff_guidcmp(&guid, &ff_asf_header))
|
|
return AVERROR_INVALIDDATA;
|
|
avio_skip(pb, 8); // skip header object size
|
|
avio_skip(pb, 6); // skip number of header objects and 2 reserved bytes
|
|
asf->data_reached = 0;
|
|
|
|
/* 1 is here instead of pb->eof_reached because (when not streaming), Data are skipped
|
|
* for the first time,
|
|
* Index object is processed and got eof and then seeking back to the Data is performed.
|
|
*/
|
|
while (1) {
|
|
// for the cases when object size is invalid
|
|
if (avio_tell(pb) == asf->offset) {
|
|
if (asf->data_reached)
|
|
avio_seek(pb, asf->first_packet_offset, SEEK_SET);
|
|
break;
|
|
}
|
|
asf->offset = avio_tell(pb);
|
|
if ((ret = ff_get_guid(pb, &guid)) < 0) {
|
|
if (ret == AVERROR_EOF && asf->data_reached) {
|
|
avio_seek(pb, asf->first_packet_offset, SEEK_SET);
|
|
break;
|
|
} else
|
|
return ret;
|
|
}
|
|
g = find_guid(guid);
|
|
if (g) {
|
|
asf->unknown_offset = asf->offset;
|
|
asf->is_header = 1;
|
|
if ((ret = g->read_object(s, g)) < 0)
|
|
return ret;
|
|
} else {
|
|
size = avio_rl64(pb);
|
|
align_position(pb, asf->offset, size);
|
|
}
|
|
if (asf->data_reached && !pb->seekable)
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < asf->nb_streams; i++) {
|
|
const char *rfc1766 = asf->asf_sd[asf->asf_st[i]->lang_idx].langs;
|
|
AVStream *st = s->streams[asf->asf_st[i]->index];
|
|
set_language(s, rfc1766, &st->metadata);
|
|
}
|
|
|
|
for (i = 0; i < ASF_MAX_STREAMS; i++) {
|
|
AVStream *st = NULL;
|
|
|
|
st = find_stream(s, i);
|
|
if (st) {
|
|
av_dict_copy(&st->metadata, asf->asf_sd[i].asf_met, AV_DICT_IGNORE_SUFFIX);
|
|
if (asf->asf_sd[i].aspect_ratio.num > 0 && asf->asf_sd[i].aspect_ratio.den > 0) {
|
|
st->sample_aspect_ratio.num = asf->asf_sd[i].aspect_ratio.num;
|
|
st->sample_aspect_ratio.den = asf->asf_sd[i].aspect_ratio.den;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
AVInputFormat ff_asf_demuxer = {
|
|
.name = "asf",
|
|
.long_name = NULL_IF_CONFIG_SMALL("ASF (Advanced / Active Streaming Format)"),
|
|
.priv_data_size = sizeof(ASFContext),
|
|
.read_probe = asf_probe,
|
|
.read_header = asf_read_header,
|
|
.read_packet = asf_read_packet,
|
|
.read_close = asf_read_close,
|
|
.read_timestamp = asf_read_timestamp,
|
|
.read_seek = asf_read_seek,
|
|
.flags = AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH,
|
|
};
|