mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-28 02:12:28 +00:00
790f793844
There are lots of files that don't need it: The number of object files that actually need it went down from 2011 to 884 here. Keep it for external users in order to not cause breakages. Also improve the other headers a bit while just at it. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
429 lines
13 KiB
C
429 lines
13 KiB
C
/*
|
|
* USM demuxer
|
|
* Copyright (c) 2023 Paul B Mahol
|
|
*
|
|
* 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/intreadwrite.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavcodec/bytestream.h"
|
|
|
|
#include "avformat.h"
|
|
#include "demux.h"
|
|
#include "internal.h"
|
|
|
|
#define VIDEOI 0
|
|
#define AUDIOI 1
|
|
#define ALPHAI 2
|
|
#define SUBTTI 3
|
|
|
|
typedef struct USMChannel {
|
|
int index;
|
|
int used;
|
|
int type;
|
|
int codec_id;
|
|
int nb_channels;
|
|
int nb_frames;
|
|
AVRational rate;
|
|
int width, height;
|
|
int64_t duration;
|
|
int64_t extradata_pos;
|
|
} USMChannel;
|
|
|
|
typedef struct USMDemuxContext {
|
|
USMChannel ch[4][256];
|
|
int nb_channels[4];
|
|
uint8_t *header;
|
|
unsigned header_size;
|
|
} USMDemuxContext;
|
|
|
|
static int usm_probe(const AVProbeData *p)
|
|
{
|
|
if (AV_RL32(p->buf) != MKTAG('C','R','I','D'))
|
|
return 0;
|
|
|
|
if (AV_RN32(p->buf + 4) == 0)
|
|
return 0;
|
|
|
|
return AVPROBE_SCORE_MAX / 3;
|
|
}
|
|
|
|
static int usm_read_header(AVFormatContext *s)
|
|
{
|
|
s->ctx_flags |= AVFMTCTX_NOHEADER;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_utf(AVFormatContext *s, AVIOContext *pb,
|
|
USMChannel *ch, int ch_type,
|
|
uint32_t parent_chunk_size)
|
|
{
|
|
USMDemuxContext *usm = s->priv_data;
|
|
GetByteContext gb, ugb, sgb;
|
|
uint32_t chunk_type, chunk_size, offset;
|
|
uint32_t unique_offset, string_offset;
|
|
int nb_items, unique_size, nb_dictionaries;
|
|
AVRational fps = { 0 };
|
|
int type;
|
|
|
|
chunk_type = avio_rb32(pb);
|
|
chunk_size = avio_rb32(pb);
|
|
|
|
if (chunk_type != MKBETAG('@','U','T','F'))
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
if (!chunk_size || chunk_size >= parent_chunk_size)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
av_fast_malloc(&usm->header, &usm->header_size, chunk_size);
|
|
if (!usm->header)
|
|
return AVERROR(ENOMEM);
|
|
|
|
if (avio_read(pb, usm->header, chunk_size) != chunk_size)
|
|
return AVERROR_EOF;
|
|
|
|
bytestream2_init(&gb, usm->header, chunk_size);
|
|
ugb = gb;
|
|
sgb = gb;
|
|
unique_offset = bytestream2_get_be32(&gb);
|
|
string_offset = bytestream2_get_be32(&gb);
|
|
/*byte_offset =*/ bytestream2_get_be32(&gb);
|
|
/*payload_name_offset =*/ bytestream2_get_be32(&gb);
|
|
nb_items = bytestream2_get_be16(&gb);
|
|
unique_size = bytestream2_get_be16(&gb);
|
|
nb_dictionaries = bytestream2_get_be32(&gb);
|
|
if (nb_dictionaries == 0)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
bytestream2_skip(&ugb, unique_offset);
|
|
if (bytestream2_get_bytes_left(&ugb) < unique_size)
|
|
return AVERROR_INVALIDDATA;
|
|
bytestream2_init(&ugb, ugb.buffer, unique_size);
|
|
|
|
bytestream2_skip(&sgb, string_offset);
|
|
|
|
for (int i = 0; i < nb_items; i++) {
|
|
GetByteContext *xgb;
|
|
uint8_t key[256];
|
|
int64_t value;
|
|
int n = 0;
|
|
|
|
type = bytestream2_get_byte(&gb);
|
|
offset = bytestream2_get_be32(&gb);
|
|
|
|
bytestream2_seek(&sgb, string_offset + offset, SEEK_SET);
|
|
while (bytestream2_get_bytes_left(&sgb) > 0) {
|
|
key[n] = bytestream2_get_byte(&sgb);
|
|
if (!key[n])
|
|
break;
|
|
if (n >= sizeof(key) - 1)
|
|
break;
|
|
n++;
|
|
}
|
|
key[n] = '\0';
|
|
|
|
if ((type >> 5) == 1)
|
|
xgb = &gb;
|
|
else
|
|
xgb = &ugb;
|
|
|
|
switch (type & 0x1F) {
|
|
case 0x10:
|
|
case 0x11:
|
|
value = bytestream2_get_byte(xgb);
|
|
break;
|
|
case 0x12:
|
|
case 0x13:
|
|
value = bytestream2_get_be16(xgb);
|
|
break;
|
|
case 0x14:
|
|
case 0x15:
|
|
value = bytestream2_get_be32(xgb);
|
|
break;
|
|
case 0x16:
|
|
case 0x17:
|
|
value = bytestream2_get_be64(xgb);
|
|
break;
|
|
case 0x18:
|
|
value = av_int2float(bytestream2_get_be32(xgb));
|
|
break;
|
|
case 0x19:
|
|
value = av_int2double(bytestream2_get_be64(xgb));
|
|
break;
|
|
case 0x1A:
|
|
break;
|
|
}
|
|
|
|
if (ch_type == AUDIOI) {
|
|
if (!strcmp(key, "sampling_rate")) {
|
|
ch->rate.num = value;
|
|
ch->rate.den = 1;
|
|
} else if (!strcmp(key, "num_channels")) {
|
|
ch->nb_channels = value;
|
|
} else if (!strcmp(key, "total_samples")) {
|
|
ch->duration = value;
|
|
} else if (!strcmp(key, "audio_codec")) {
|
|
switch (value) {
|
|
case 2:
|
|
ch->codec_id = AV_CODEC_ID_ADPCM_ADX;
|
|
break;
|
|
case 4:
|
|
ch->codec_id = AV_CODEC_ID_HCA;
|
|
break;
|
|
default:
|
|
av_log(s, AV_LOG_ERROR, "unsupported audio: %d\n", (int)value);
|
|
break;
|
|
}
|
|
}
|
|
} else if (ch_type == VIDEOI || ch_type == ALPHAI) {
|
|
if (!strcmp(key, "width")) {
|
|
ch->width = value;
|
|
} else if (!strcmp(key, "height")) {
|
|
ch->height = value;
|
|
} else if (!strcmp(key, "total_frames")) {
|
|
ch->nb_frames = value;
|
|
} else if (!strcmp(key, "framerate_n")) {
|
|
fps.num = value;
|
|
} else if (!strcmp(key, "framerate_d")) {
|
|
fps.den = value;
|
|
} else if (!strcmp(key, "mpeg_codec")) {
|
|
switch (value) {
|
|
case 1:
|
|
ch->codec_id = AV_CODEC_ID_MPEG1VIDEO;
|
|
break;
|
|
case 5:
|
|
ch->codec_id = AV_CODEC_ID_H264;
|
|
break;
|
|
case 9:
|
|
ch->codec_id = AV_CODEC_ID_VP9;
|
|
break;
|
|
default:
|
|
av_log(s, AV_LOG_ERROR, "unsupported video: %d\n", (int)value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ch_type == VIDEOI && fps.num && fps.den)
|
|
ch->rate = fps;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int64_t parse_chunk(AVFormatContext *s, AVIOContext *pb,
|
|
uint32_t chunk_type, uint32_t chunk_size,
|
|
AVPacket *pkt)
|
|
{
|
|
const int is_audio = chunk_type == MKBETAG('@','S','F','A');
|
|
const int is_alpha = chunk_type == MKBETAG('@','A','L','P');
|
|
const int is_subtt = chunk_type == MKBETAG('@','S','B','T');
|
|
USMDemuxContext *usm = s->priv_data;
|
|
int padding_size, payload_type, payload_offset;
|
|
const int ch_type = is_subtt ? SUBTTI : is_audio ? AUDIOI : is_alpha ? ALPHAI : VIDEOI;
|
|
int stream_index, frame_rate;
|
|
int64_t chunk_start, ret;
|
|
|
|
ret = avio_tell(pb);
|
|
if (ret < 0)
|
|
return ret;
|
|
chunk_start = ret;
|
|
avio_skip(pb, 1);
|
|
payload_offset = avio_r8(pb);
|
|
padding_size = avio_rb16(pb);
|
|
stream_index = avio_r8(pb);
|
|
avio_skip(pb, 2);
|
|
payload_type = avio_r8(pb);
|
|
/*frame_time =*/ avio_rb32(pb);
|
|
frame_rate = avio_rb32(pb);
|
|
avio_skip(pb, 8);
|
|
ret = avio_tell(pb);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = avio_skip(pb, FFMAX(0, (ret - chunk_start) - payload_offset));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (payload_type == 1) {
|
|
if (usm->ch[ch_type][stream_index].used == 0) {
|
|
USMChannel *ch = &usm->ch[ch_type][stream_index];
|
|
|
|
switch (ch_type) {
|
|
case ALPHAI:
|
|
case VIDEOI:
|
|
ch->type = AVMEDIA_TYPE_VIDEO;
|
|
break;
|
|
case AUDIOI:
|
|
ch->type = AVMEDIA_TYPE_AUDIO;
|
|
break;
|
|
case SUBTTI:
|
|
ch->type = AVMEDIA_TYPE_SUBTITLE;
|
|
break;
|
|
default:
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
|
|
ch->used = 1;
|
|
ch->index = -1;
|
|
usm->nb_channels[ch_type]++;
|
|
|
|
ret = parse_utf(s, pb, ch, ch_type, chunk_size);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
} else if (payload_type == 0) {
|
|
if (usm->ch[ch_type][stream_index].used == 1) {
|
|
USMChannel *ch = &usm->ch[ch_type][stream_index];
|
|
int get_extradata = 0;
|
|
uint32_t pkt_size;
|
|
AVStream *st;
|
|
|
|
if (ch->index < 0) {
|
|
AVCodecParameters *par;
|
|
st = avformat_new_stream(s, NULL);
|
|
if (!st)
|
|
return AVERROR(ENOMEM);
|
|
par = st->codecpar;
|
|
par->codec_type = ch->type;
|
|
par->codec_id = ch->codec_id;
|
|
st->start_time = 0;
|
|
|
|
switch (ch->type) {
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
par->width = ch->width;
|
|
par->height = ch->height;
|
|
st->nb_frames = ch->nb_frames;
|
|
break;
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
par->sample_rate = ch->rate.num;
|
|
par->ch_layout.nb_channels = ch->nb_channels;
|
|
st->duration = ch->duration;
|
|
break;
|
|
}
|
|
|
|
ch->index = st->index;
|
|
if (!ch->rate.num || !ch->rate.den)
|
|
ch->rate = av_make_q(frame_rate, 100);
|
|
avpriv_set_pts_info(st, 64, ch->rate.den, ch->rate.num);
|
|
|
|
ffstream(st)->need_parsing = AVSTREAM_PARSE_TIMESTAMPS;
|
|
get_extradata = ch->codec_id == AV_CODEC_ID_ADPCM_ADX;
|
|
ch->extradata_pos = avio_tell(pb);
|
|
}
|
|
|
|
ret = avio_tell(pb);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
pkt_size = chunk_size - (ret - chunk_start) - padding_size;
|
|
if (get_extradata) {
|
|
if ((ret = ff_get_extradata(s, st->codecpar, pb, pkt_size)) < 0)
|
|
return ret;
|
|
} else {
|
|
if (ret == ch->extradata_pos && ch->codec_id == AV_CODEC_ID_ADPCM_ADX) {
|
|
avio_skip(pb, pkt_size);
|
|
ret = 0;
|
|
} else {
|
|
ret = av_get_packet(pb, pkt, pkt_size);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
pkt->stream_index = ch->index;
|
|
}
|
|
}
|
|
|
|
avio_skip(pb, padding_size);
|
|
|
|
if (ret != pkt_size)
|
|
return AVERROR_EOF;
|
|
if (get_extradata == 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = avio_tell(pb);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = avio_skip(pb, FFMAX(0, chunk_size - (ret - chunk_start)));
|
|
if (ret < 0)
|
|
return ret;
|
|
return FFERROR_REDO;
|
|
}
|
|
|
|
static int usm_read_packet(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
AVIOContext *pb = s->pb;
|
|
int64_t ret = AVERROR_EOF;
|
|
|
|
while (!avio_feof(pb)) {
|
|
uint32_t chunk_type, chunk_size;
|
|
int got_packet = 0;
|
|
int64_t pos;
|
|
|
|
pos = avio_tell(pb);
|
|
if (pos < 0)
|
|
return pos;
|
|
chunk_type = avio_rb32(pb);
|
|
chunk_size = avio_rb32(pb);
|
|
if (!chunk_size)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
switch (chunk_type) {
|
|
case MKBETAG('C','R','I','D'):
|
|
default:
|
|
ret = avio_skip(pb, chunk_size);
|
|
break;
|
|
case MKBETAG('@','A','L','P'):
|
|
case MKBETAG('@','S','B','T'):
|
|
case MKBETAG('@','S','F','A'):
|
|
case MKBETAG('@','S','F','V'):
|
|
ret = parse_chunk(s, pb, chunk_type, chunk_size, pkt);
|
|
got_packet = ret > 0;
|
|
break;
|
|
}
|
|
|
|
if (got_packet)
|
|
pkt->pos = pos;
|
|
|
|
if (got_packet || ret < 0)
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int usm_read_close(AVFormatContext *s)
|
|
{
|
|
USMDemuxContext *usm = s->priv_data;
|
|
av_freep(&usm->header);
|
|
usm->header_size = 0;
|
|
return 0;
|
|
}
|
|
|
|
const FFInputFormat ff_usm_demuxer = {
|
|
.p.name = "usm",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("CRI USM"),
|
|
.p.extensions = "usm",
|
|
.p.flags = AVFMT_GENERIC_INDEX | AVFMT_NO_BYTE_SEEK | AVFMT_NOBINSEARCH,
|
|
.priv_data_size = sizeof(USMDemuxContext),
|
|
.read_probe = usm_probe,
|
|
.read_header = usm_read_header,
|
|
.read_packet = usm_read_packet,
|
|
.read_close = usm_read_close,
|
|
};
|