mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-27 09:52:17 +00:00
9200514ad8
Currently, AVStream contains an embedded AVCodecContext instance, which is used by demuxers to export stream parameters to the caller and by muxers to receive stream parameters from the caller. It is also used internally as the codec context that is passed to parsers. In addition, it is also widely used by the callers as the decoding (when demuxer) or encoding (when muxing) context, though this has been officially discouraged since Libav 11. There are multiple important problems with this approach: - the fields in AVCodecContext are in general one of * stream parameters * codec options * codec state However, it's not clear which ones are which. It is consequently unclear which fields are a demuxer allowed to set or a muxer allowed to read. This leads to erratic behaviour depending on whether decoding or encoding is being performed or not (and whether it uses the AVStream embedded codec context). - various synchronization issues arising from the fact that the same context is used by several different APIs (muxers/demuxers, parsers, bitstream filters and encoders/decoders) simultaneously, with there being no clear rules for who can modify what and the different processes being typically delayed with respect to each other. - avformat_find_stream_info() making it necessary to support opening and closing a single codec context multiple times, thus complicating the semantics of freeing various allocated objects in the codec context. Those problems are resolved by replacing the AVStream embedded codec context with a newly added AVCodecParameters instance, which stores only the stream parameters exported by the demuxers or read by the muxers.
308 lines
10 KiB
C
308 lines
10 KiB
C
/*
|
|
* QDesign Music 2 (QDM2) payload for RTP
|
|
* Copyright (c) 2010 Ronald S. Bultje
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief RTP support for the QDM2 payload (todo: wiki)
|
|
* @author Ronald S. Bultje <rbultje@ronald.bitfreak.net>
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "libavutil/intreadwrite.h"
|
|
#include "libavcodec/avcodec.h"
|
|
#include "rtp.h"
|
|
#include "rtpdec.h"
|
|
#include "rtpdec_formats.h"
|
|
|
|
struct PayloadContext {
|
|
/** values read from the config header, used as packet headers */
|
|
//@{
|
|
int block_type; ///< superblock type, value 2 .. 8
|
|
int block_size; ///< from extradata, used as pkt length
|
|
int subpkts_per_block; ///< max. nr. of subpackets to add per output buffer
|
|
//@}
|
|
|
|
/** Temporary storage for superblock restoring, per packet ID (0x80 total) */
|
|
//@{
|
|
uint16_t len[0x80]; ///< how much the temporary buffer is filled
|
|
uint8_t buf[0x80][0x800]; ///< the temporary storage buffer
|
|
|
|
unsigned int cache; ///< number of data packets that we have cached right now
|
|
unsigned int n_pkts; ///< number of RTP packets received since last packet output / config
|
|
uint32_t timestamp; ///< timestamp of next-to-be-returned packet
|
|
//@}
|
|
};
|
|
|
|
/**
|
|
* Parse configuration (basically the codec-specific extradata) from
|
|
* an RTP config subpacket (starts with 0xff).
|
|
*
|
|
* Layout of the config subpacket (in bytes):
|
|
* 1: 0xFF <- config ID
|
|
* then an array {
|
|
* 1: size <- of the current item
|
|
* 1: item type <- 0 .. 4
|
|
* size-2: data <- data depends on the item type
|
|
* }
|
|
*
|
|
* Item 0 implies the end of the config subpacket, and has no data.
|
|
* Item 1 implies a stream configuration without extradata.
|
|
* Item 2 max. nr. of subpackets per superblock
|
|
* Item 3 superblock type for the stream
|
|
* Item 4 implies a stream configuration with extradata (size >= 0x1c).
|
|
*
|
|
* @return <0 on error, otherwise the number of bytes parsed from the
|
|
* input buffer.
|
|
*/
|
|
static int qdm2_parse_config(PayloadContext *qdm, AVStream *st,
|
|
const uint8_t *buf, const uint8_t *end)
|
|
{
|
|
const uint8_t *p = buf;
|
|
|
|
while (end - p >= 2) {
|
|
unsigned int item_len = p[0], config_item = p[1];
|
|
|
|
if (item_len < 2 || end - p < item_len || config_item > 4)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
switch (config_item) {
|
|
case 0: /* end of config block */
|
|
return p - buf + item_len;
|
|
case 1: /* stream without extradata */
|
|
/* FIXME: set default qdm->block_size */
|
|
break;
|
|
case 2: /**< subpackets per block */
|
|
if (item_len < 3)
|
|
return AVERROR_INVALIDDATA;
|
|
qdm->subpkts_per_block = p[2];
|
|
break;
|
|
case 3: /* superblock type */
|
|
if (item_len < 4)
|
|
return AVERROR_INVALIDDATA;
|
|
qdm->block_type = AV_RB16(p + 2);
|
|
break;
|
|
case 4: /* stream with extradata */
|
|
if (item_len < 30)
|
|
return AVERROR_INVALIDDATA;
|
|
av_freep(&st->codecpar->extradata);
|
|
st->codecpar->extradata_size = 26 + item_len;
|
|
if (!(st->codecpar->extradata = av_mallocz(st->codecpar->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE))) {
|
|
st->codecpar->extradata_size = 0;
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
AV_WB32(st->codecpar->extradata, 12);
|
|
memcpy(st->codecpar->extradata + 4, "frma", 4);
|
|
memcpy(st->codecpar->extradata + 8, "QDM2", 4);
|
|
AV_WB32(st->codecpar->extradata + 12, 6 + item_len);
|
|
memcpy(st->codecpar->extradata + 16, "QDCA", 4);
|
|
memcpy(st->codecpar->extradata + 20, p + 2, item_len - 2);
|
|
AV_WB32(st->codecpar->extradata + 18 + item_len, 8);
|
|
AV_WB32(st->codecpar->extradata + 22 + item_len, 0);
|
|
|
|
qdm->block_size = AV_RB32(p + 26);
|
|
break;
|
|
}
|
|
|
|
p += item_len;
|
|
}
|
|
|
|
return AVERROR(EAGAIN); /* not enough data */
|
|
}
|
|
|
|
/**
|
|
* Parse a single subpacket. We store this subpacket in an intermediate
|
|
* buffer (position depends on the ID (byte[0]). When called, at least
|
|
* 4 bytes are available for reading (see qdm2_parse_packet()).
|
|
*
|
|
* Layout of a single subpacket (RTP packets commonly contain multiple
|
|
* such subpackets) - length in bytes:
|
|
* 1: ordering ID <- 0 .. 0x7F
|
|
* 1: subpacket type <- 0 .. 0x7F; value & 0x80 means subpacket length = 2 bytes, else 1 byte
|
|
* 1/2: subpacket length <- length of the data following the flags/length fields
|
|
* if (subpacket type & 0x7F) == 0x7F
|
|
* 1: subpacket type, higher bits
|
|
* size: subpacket data
|
|
*
|
|
* The subpackets come in randomly, and should be encapsulated into 1
|
|
* or more superblocks (containing qdm->subpkts_per_block subpackets
|
|
* each) per RTP packet, in order of ascending "ordering ID", see
|
|
* qdm2_restore_block().
|
|
*
|
|
* @return <0 on error, otherwise the number of bytes parsed from the
|
|
* input buffer.
|
|
*/
|
|
static int qdm2_parse_subpacket(PayloadContext *qdm, AVStream *st,
|
|
const uint8_t *buf, const uint8_t *end)
|
|
{
|
|
const uint8_t *p = buf;
|
|
unsigned int id, len, type, to_copy;
|
|
|
|
/* parse header so we know the size of the header/data */
|
|
id = *p++;
|
|
type = *p++;
|
|
if (type & 0x80) {
|
|
len = AV_RB16(p);
|
|
p += 2;
|
|
type &= 0x7F;
|
|
} else
|
|
len = *p++;
|
|
|
|
if (end - p < len + (type == 0x7F) || id >= 0x80)
|
|
return AVERROR_INVALIDDATA;
|
|
if (type == 0x7F)
|
|
type |= *p++ << 8;
|
|
|
|
/* copy data into a temporary buffer */
|
|
to_copy = FFMIN(len + (p - &buf[1]), 0x800 - qdm->len[id]);
|
|
memcpy(&qdm->buf[id][qdm->len[id]], buf + 1, to_copy);
|
|
qdm->len[id] += to_copy;
|
|
|
|
return p + len - buf;
|
|
}
|
|
|
|
/**
|
|
* Add a superblock header around a set of subpackets.
|
|
*
|
|
* @return <0 on error, else 0.
|
|
*/
|
|
static int qdm2_restore_block(PayloadContext *qdm, AVStream *st, AVPacket *pkt)
|
|
{
|
|
int to_copy, n, res, include_csum;
|
|
uint8_t *p, *csum_pos = NULL;
|
|
|
|
/* create packet to hold subpkts into a superblock */
|
|
assert(qdm->cache > 0);
|
|
for (n = 0; n < 0x80; n++)
|
|
if (qdm->len[n] > 0)
|
|
break;
|
|
assert(n < 0x80);
|
|
|
|
if ((res = av_new_packet(pkt, qdm->block_size)) < 0)
|
|
return res;
|
|
memset(pkt->data, 0, pkt->size);
|
|
pkt->stream_index = st->index;
|
|
p = pkt->data;
|
|
|
|
/* superblock header */
|
|
if (qdm->len[n] > 0xff) {
|
|
*p++ = qdm->block_type | 0x80;
|
|
AV_WB16(p, qdm->len[n]);
|
|
p += 2;
|
|
} else {
|
|
*p++ = qdm->block_type;
|
|
*p++ = qdm->len[n];
|
|
}
|
|
if ((include_csum = (qdm->block_type == 2 || qdm->block_type == 4))) {
|
|
csum_pos = p;
|
|
p += 2;
|
|
}
|
|
|
|
/* subpacket data */
|
|
to_copy = FFMIN(qdm->len[n], pkt->size - (p - pkt->data));
|
|
memcpy(p, qdm->buf[n], to_copy);
|
|
qdm->len[n] = 0;
|
|
|
|
/* checksum header */
|
|
if (include_csum) {
|
|
unsigned int total = 0;
|
|
uint8_t *q;
|
|
|
|
for (q = pkt->data; q < &pkt->data[qdm->block_size]; q++)
|
|
total += *q;
|
|
AV_WB16(csum_pos, (uint16_t) total);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** return 0 on packet, no more left, 1 on packet, -1 on partial packet... */
|
|
static int qdm2_parse_packet(AVFormatContext *s, PayloadContext *qdm,
|
|
AVStream *st, AVPacket *pkt,
|
|
uint32_t *timestamp,
|
|
const uint8_t *buf, int len, uint16_t seq,
|
|
int flags)
|
|
{
|
|
int res = AVERROR_INVALIDDATA, n;
|
|
const uint8_t *end = buf + len, *p = buf;
|
|
|
|
if (len > 0) {
|
|
if (len < 2)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
/* configuration block */
|
|
if (*p == 0xff) {
|
|
if (qdm->n_pkts > 0) {
|
|
av_log(s, AV_LOG_WARNING,
|
|
"Out of sequence config - dropping queue\n");
|
|
qdm->n_pkts = 0;
|
|
memset(qdm->len, 0, sizeof(qdm->len));
|
|
}
|
|
|
|
if ((res = qdm2_parse_config(qdm, st, ++p, end)) < 0)
|
|
return res;
|
|
p += res;
|
|
|
|
/* We set codec_id to AV_CODEC_ID_NONE initially to
|
|
* delay decoder initialization since extradata is
|
|
* carried within the RTP stream, not SDP. Here,
|
|
* by setting codec_id to AV_CODEC_ID_QDM2, we are signalling
|
|
* to the decoder that it is OK to initialize. */
|
|
st->codecpar->codec_id = AV_CODEC_ID_QDM2;
|
|
}
|
|
if (st->codecpar->codec_id == AV_CODEC_ID_NONE)
|
|
return AVERROR(EAGAIN);
|
|
|
|
/* subpackets */
|
|
while (end - p >= 4) {
|
|
if ((res = qdm2_parse_subpacket(qdm, st, p, end)) < 0)
|
|
return res;
|
|
p += res;
|
|
}
|
|
|
|
qdm->timestamp = *timestamp;
|
|
if (++qdm->n_pkts < qdm->subpkts_per_block)
|
|
return AVERROR(EAGAIN);
|
|
qdm->cache = 0;
|
|
for (n = 0; n < 0x80; n++)
|
|
if (qdm->len[n] > 0)
|
|
qdm->cache++;
|
|
}
|
|
|
|
/* output the subpackets into freshly created superblock structures */
|
|
if (!qdm->cache || (res = qdm2_restore_block(qdm, st, pkt)) < 0)
|
|
return res;
|
|
if (--qdm->cache == 0)
|
|
qdm->n_pkts = 0;
|
|
|
|
*timestamp = qdm->timestamp;
|
|
qdm->timestamp = RTP_NOTS_VALUE;
|
|
|
|
return (qdm->cache > 0) ? 1 : 0;
|
|
}
|
|
|
|
RTPDynamicProtocolHandler ff_qdm2_dynamic_handler = {
|
|
.enc_name = "X-QDM",
|
|
.codec_type = AVMEDIA_TYPE_AUDIO,
|
|
.codec_id = AV_CODEC_ID_NONE,
|
|
.priv_data_size = sizeof(PayloadContext),
|
|
.parse_packet = qdm2_parse_packet,
|
|
};
|