mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 20:21:24 +00:00
4a28e649c3
Every g_quark_from_static_string() is a hash table lookup serialised on the global quark lock in GLib. Let's just look up the two quarks we need once and cache them locally for future use. While we're at it, add new utility functions for the two most commonly used tags (audio + video). Make first argument a gpointer so we don't have to cast and make the code ugly. These are used for logging purposes only anyway.
1071 lines
30 KiB
C
1071 lines
30 KiB
C
/* GStreamer
|
|
* Copyright (C) <2014> Stian Selnes <stian@pexip.com>
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-rtph261pay
|
|
* @see_also: rtph261depay
|
|
*
|
|
* Payload encoded H.261 video frames into RTP packets according to RFC 4587.
|
|
* For detailed information see: https://www.rfc-editor.org/rfc/rfc4587.txt
|
|
*
|
|
* The payloader takes a H.261 frame, parses it and splits it into fragments
|
|
* on MB boundaries in order to match configured MTU size. For each fragment
|
|
* an RTP packet is constructed with an RTP packet header followed by the
|
|
* fragment. In addition the payloader will make sure the packetized H.261
|
|
* stream appears as a continuous bit-stream after depacketization by shifting
|
|
* the encoded bit-stream of a frame to align with the last significant bit of
|
|
* the previous frame. This helps interoperability in the case where the
|
|
* encoder does not produce a continuous bit-stream but the decoder requires
|
|
* it.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 videotestsrc ! avenc_h261 ! rtph261pay ! udpsink
|
|
* ]| This will encode a test video and payload it. Refer to the rtph261depay
|
|
* example to depayload and play the RTP stream.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "gstrtph261pay.h"
|
|
#include "gstrtputils.h"
|
|
#include <gst/rtp/gstrtpbuffer.h>
|
|
#include <gst/video/video.h>
|
|
#include <gst/base/gstbitreader.h>
|
|
#include <string.h>
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (rtph261pay_debug);
|
|
#define GST_CAT_DEFAULT (rtph261pay_debug)
|
|
|
|
#define GST_RTP_HEADER_LEN 12
|
|
#define GST_RTP_H261_PAYLOAD_HEADER_LEN 4
|
|
|
|
static GstStaticPadTemplate gst_rtp_h261_pay_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-h261")
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_rtp_h261_pay_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-rtp, "
|
|
"media = (string) \"video\", "
|
|
"payload = (int) " GST_RTP_PAYLOAD_H261_STRING ", "
|
|
"clock-rate = (int) 90000, " "encoding-name = (string) \"H261\"; "
|
|
"application/x-rtp, "
|
|
"media = (string) \"video\", "
|
|
"payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
|
|
"clock-rate = (int) 90000, " "encoding-name = (string) \"H261\"")
|
|
);
|
|
|
|
G_DEFINE_TYPE (GstRtpH261Pay, gst_rtp_h261_pay, GST_TYPE_RTP_BASE_PAYLOAD);
|
|
#define parent_class gst_rtp_h261_pay_parent_class
|
|
|
|
typedef struct
|
|
{
|
|
guint32 mba;
|
|
guint32 mtype;
|
|
guint32 quant;
|
|
gint mvx;
|
|
gint mvy;
|
|
guint endpos;
|
|
gint gobn;
|
|
} Macroblock;
|
|
|
|
typedef struct
|
|
{
|
|
Macroblock last;
|
|
guint startpos;
|
|
guint endpos;
|
|
guint32 gn;
|
|
guint32 gquant;
|
|
} Gob;
|
|
|
|
#define PSC_LEN 20
|
|
#define TR_LEN 5
|
|
#define PTYPE_LEN 6
|
|
#define GBSC_LEN 16
|
|
#define GN_LEN 4
|
|
#define GQUANT_LEN 5
|
|
#define GEI_LEN 1
|
|
#define GSPARE_LEN 8
|
|
#define MQUANT_LEN 5
|
|
#define MAX_NUM_GOB 12
|
|
|
|
typedef enum
|
|
{
|
|
PARSE_END_OF_BUFFER = -2,
|
|
PARSE_ERROR = -1,
|
|
PARSE_OK = 0,
|
|
PARSE_END_OF_FRAME,
|
|
PARSE_END_OF_GOB,
|
|
} ParseReturn;
|
|
|
|
|
|
#define SKIP_BITS(br,nbits) G_STMT_START { \
|
|
if (!gst_bit_reader_skip (br, nbits)) \
|
|
return PARSE_END_OF_BUFFER; \
|
|
} G_STMT_END
|
|
|
|
#define GET_BITS(br,val,nbits) G_STMT_START { \
|
|
if (!gst_bit_reader_get_bits_uint32 (br, val, nbits)) \
|
|
return PARSE_END_OF_BUFFER; \
|
|
} G_STMT_END
|
|
|
|
/* Unchecked since we peek outside the buffer. Ok because of padding. */
|
|
#define PEEK_BITS(br,val,nbits) G_STMT_START { \
|
|
*val = gst_bit_reader_peek_bits_uint16_unchecked (br, nbits); \
|
|
} G_STMT_END
|
|
|
|
|
|
#define MBA_STUFFING 34
|
|
#define MBA_START_CODE 35
|
|
#define MBA_LEN 35
|
|
#define MBA_WID 4
|
|
/* [code, mask, nbits, mba] */
|
|
static const guint16 mba_table[MBA_LEN][MBA_WID] = {
|
|
{0x8000, 0x8000, 1, 1},
|
|
{0x6000, 0xe000, 3, 2},
|
|
{0x4000, 0xe000, 3, 3},
|
|
{0x3000, 0xf000, 4, 4},
|
|
{0x2000, 0xf000, 4, 5},
|
|
{0x1800, 0xf800, 5, 6},
|
|
{0x1000, 0xf800, 5, 7},
|
|
{0x0e00, 0xfe00, 7, 8},
|
|
{0x0c00, 0xfe00, 7, 9},
|
|
{0x0b00, 0xff00, 8, 10},
|
|
{0x0a00, 0xff00, 8, 11},
|
|
{0x0900, 0xff00, 8, 12},
|
|
{0x0800, 0xff00, 8, 13},
|
|
{0x0700, 0xff00, 8, 14},
|
|
{0x0600, 0xff00, 8, 15},
|
|
{0x05c0, 0xffc0, 10, 16},
|
|
{0x0580, 0xffc0, 10, 17},
|
|
{0x0540, 0xffc0, 10, 18},
|
|
{0x0500, 0xffc0, 10, 19},
|
|
{0x04c0, 0xffc0, 10, 20},
|
|
{0x0480, 0xffc0, 10, 21},
|
|
{0x0460, 0xffe0, 11, 22},
|
|
{0x0440, 0xffe0, 11, 23},
|
|
{0x0420, 0xffe0, 11, 24},
|
|
{0x0400, 0xffe0, 11, 25},
|
|
{0x03e0, 0xffe0, 11, 26},
|
|
{0x03c0, 0xffe0, 11, 27},
|
|
{0x03a0, 0xffe0, 11, 28},
|
|
{0x0380, 0xffe0, 11, 29},
|
|
{0x0360, 0xffe0, 11, 30},
|
|
{0x0340, 0xffe0, 11, 31},
|
|
{0x0320, 0xffe0, 11, 32},
|
|
{0x0300, 0xffe0, 11, 33},
|
|
{0x01e0, 0xffe0, 11, MBA_STUFFING},
|
|
{0x0001, 0xffff, 16, MBA_START_CODE},
|
|
};
|
|
|
|
#define MTYPE_INTRA (1 << 0)
|
|
#define MTYPE_INTER (1 << 1)
|
|
#define MTYPE_MC (1 << 2)
|
|
#define MTYPE_FIL (1 << 3)
|
|
#define MTYPE_MQUANT (1 << 4)
|
|
#define MTYPE_MVD (1 << 5)
|
|
#define MTYPE_CBP (1 << 6)
|
|
#define MTYPE_TCOEFF (1 << 7)
|
|
#define MTYPE_LEN 10
|
|
#define MTYPE_WID 4
|
|
/* [code, mask, nbits, flags] */
|
|
static const guint16 mtype_table[MTYPE_LEN][MTYPE_WID] = {
|
|
{0x8000, 0x8000, 1, MTYPE_INTER | MTYPE_CBP | MTYPE_TCOEFF},
|
|
{0x4000, 0xc000, 2,
|
|
MTYPE_INTER | MTYPE_MC | MTYPE_FIL | MTYPE_MVD | MTYPE_CBP |
|
|
MTYPE_TCOEFF},
|
|
{0x2000, 0xe000, 3, MTYPE_INTER | MTYPE_MC | MTYPE_FIL | MTYPE_MVD},
|
|
{0x1000, 0xf000, 4, MTYPE_INTRA | MTYPE_TCOEFF},
|
|
{0x0800, 0xf800, 5, MTYPE_INTER | MTYPE_MQUANT | MTYPE_CBP | MTYPE_TCOEFF},
|
|
{0x0400, 0xfc00, 6,
|
|
MTYPE_INTER | MTYPE_MC | MTYPE_FIL | MTYPE_MQUANT | MTYPE_MVD |
|
|
MTYPE_CBP | MTYPE_TCOEFF},
|
|
{0x0200, 0xfe00, 7, MTYPE_INTRA | MTYPE_MQUANT | MTYPE_TCOEFF},
|
|
{0x0100, 0xff00, 8,
|
|
MTYPE_INTER | MTYPE_MC | MTYPE_MVD | MTYPE_CBP | MTYPE_TCOEFF},
|
|
{0x0080, 0xff80, 9, MTYPE_INTER | MTYPE_MC | MTYPE_MVD},
|
|
{0x0040, 0xffc0, 10,
|
|
MTYPE_INTER | MTYPE_MC | MTYPE_MQUANT | MTYPE_MVD | MTYPE_CBP |
|
|
MTYPE_TCOEFF},
|
|
};
|
|
|
|
#define MVD_LEN 32
|
|
#define MVD_WID 5
|
|
/* [code, mask, nbits, mvd1, mvd2] */
|
|
static const guint16 mvd_table[MVD_LEN][MVD_WID] = {
|
|
{0x8000, 0x8000, 1, 0, 0},
|
|
{0x6000, 0xe000, 3, -1, -1},
|
|
{0x4000, 0xe000, 3, 1, 1},
|
|
{0x3000, 0xf000, 4, -2, 30},
|
|
{0x2000, 0xf000, 4, 2, -30},
|
|
{0x1800, 0xf800, 5, -3, 29},
|
|
{0x1000, 0xf800, 5, 3, -29},
|
|
{0x0e00, 0xfe00, 7, -4, 28},
|
|
{0x0c00, 0xfe00, 7, 4, -28},
|
|
{0x0700, 0xff00, 8, -7, 25},
|
|
{0x0900, 0xff00, 8, -6, 26},
|
|
{0x0b00, 0xff00, 8, -5, 27},
|
|
{0x0a00, 0xff00, 8, 5, -27},
|
|
{0x0800, 0xff00, 8, 6, -26},
|
|
{0x0600, 0xff00, 8, 7, -25},
|
|
{0x04c0, 0xffc0, 10, -10, 22},
|
|
{0x0540, 0xffc0, 10, -9, 23},
|
|
{0x05c0, 0xffc0, 10, -8, 24},
|
|
{0x0580, 0xffc0, 10, 8, -24},
|
|
{0x0500, 0xffc0, 10, 9, -23},
|
|
{0x0480, 0xffc0, 10, 10, -22},
|
|
{0x0320, 0xffe0, 11, -16, 16},
|
|
{0x0360, 0xffe0, 11, -15, 17},
|
|
{0x03a0, 0xffe0, 11, -14, 18},
|
|
{0x03e0, 0xffe0, 11, -13, 19},
|
|
{0x0420, 0xffe0, 11, -12, 20},
|
|
{0x0460, 0xffe0, 11, -11, 21},
|
|
{0x0440, 0xffe0, 11, 11, -21},
|
|
{0x0400, 0xffe0, 11, 12, -20},
|
|
{0x03c0, 0xffe0, 11, 13, -19},
|
|
{0x0380, 0xffe0, 11, 14, -18},
|
|
{0x0340, 0xffe0, 11, 15, -17},
|
|
};
|
|
|
|
#define CBP_LEN 63
|
|
/* [code, mask, nbits, cbp] */
|
|
static const guint16 cbp_table[CBP_LEN][4] = {
|
|
{0xe000, 0xe000, 3, 60},
|
|
{0xd000, 0xf000, 4, 4},
|
|
{0xc000, 0xf000, 4, 8},
|
|
{0xb000, 0xf000, 4, 16},
|
|
{0xa000, 0xf000, 4, 32},
|
|
{0x9800, 0xf800, 5, 12},
|
|
{0x9000, 0xf800, 5, 48},
|
|
{0x8800, 0xf800, 5, 20},
|
|
{0x8000, 0xf800, 5, 40},
|
|
{0x7800, 0xf800, 5, 28},
|
|
{0x7000, 0xf800, 5, 44},
|
|
{0x6800, 0xf800, 5, 52},
|
|
{0x6000, 0xf800, 5, 56},
|
|
{0x5800, 0xf800, 5, 1},
|
|
{0x5000, 0xf800, 5, 61},
|
|
{0x4800, 0xf800, 5, 2},
|
|
{0x4000, 0xf800, 5, 62},
|
|
{0x3c00, 0xfc00, 6, 24},
|
|
{0x3800, 0xfc00, 6, 36},
|
|
{0x3400, 0xfc00, 6, 3},
|
|
{0x3000, 0xfc00, 6, 63},
|
|
{0x2e00, 0xfe00, 7, 5},
|
|
{0x2c00, 0xfe00, 7, 9},
|
|
{0x2a00, 0xfe00, 7, 17},
|
|
{0x2800, 0xfe00, 7, 33},
|
|
{0x2600, 0xfe00, 7, 6},
|
|
{0x2400, 0xfe00, 7, 10},
|
|
{0x2200, 0xfe00, 7, 18},
|
|
{0x2000, 0xfe00, 7, 34},
|
|
{0x1f00, 0xff00, 8, 7},
|
|
{0x1e00, 0xff00, 8, 11},
|
|
{0x1d00, 0xff00, 8, 19},
|
|
{0x1c00, 0xff00, 8, 35},
|
|
{0x1b00, 0xff00, 8, 13},
|
|
{0x1a00, 0xff00, 8, 49},
|
|
{0x1900, 0xff00, 8, 21},
|
|
{0x1800, 0xff00, 8, 41},
|
|
{0x1700, 0xff00, 8, 14},
|
|
{0x1600, 0xff00, 8, 50},
|
|
{0x1500, 0xff00, 8, 22},
|
|
{0x1400, 0xff00, 8, 42},
|
|
{0x1300, 0xff00, 8, 15},
|
|
{0x1200, 0xff00, 8, 51},
|
|
{0x1100, 0xff00, 8, 23},
|
|
{0x1000, 0xff00, 8, 43},
|
|
{0x0f00, 0xff00, 8, 25},
|
|
{0x0e00, 0xff00, 8, 37},
|
|
{0x0d00, 0xff00, 8, 26},
|
|
{0x0c00, 0xff00, 8, 38},
|
|
{0x0b00, 0xff00, 8, 29},
|
|
{0x0a00, 0xff00, 8, 45},
|
|
{0x0900, 0xff00, 8, 53},
|
|
{0x0800, 0xff00, 8, 57},
|
|
{0x0700, 0xff00, 8, 30},
|
|
{0x0600, 0xff00, 8, 46},
|
|
{0x0500, 0xff00, 8, 54},
|
|
{0x0400, 0xff00, 8, 58},
|
|
{0x0380, 0xff80, 9, 31},
|
|
{0x0300, 0xff80, 9, 47},
|
|
{0x0280, 0xff80, 9, 55},
|
|
{0x0200, 0xff80, 9, 59},
|
|
{0x0180, 0xff80, 9, 27},
|
|
{0x0100, 0xff80, 9, 39},
|
|
};
|
|
|
|
#define TCOEFF_EOB 0xffff
|
|
#define TCOEFF_ESC 0xfffe
|
|
#define TCOEFF_LEN 65
|
|
/* [code, mask, nbits, run, level] */
|
|
static const guint16 tcoeff_table[TCOEFF_LEN][5] = {
|
|
{0x8000, 0xc000, 2, TCOEFF_EOB, 0}, /* Not available for first coeff */
|
|
/* {0x8000, 0x8000, 2, 0, 1}, *//* Available only for first Inter coeff */
|
|
{0xc000, 0xc000, 3, 0, 1}, /* Not available for first coeff */
|
|
{0x6000, 0xe000, 4, 1, 1},
|
|
{0x4000, 0xf000, 5, 0, 2},
|
|
{0x5000, 0xf000, 5, 2, 1},
|
|
{0x2800, 0xf800, 6, 0, 3},
|
|
{0x3800, 0xf800, 6, 3, 1},
|
|
{0x3000, 0xf800, 6, 4, 1},
|
|
{0x0400, 0xfc00, 6, TCOEFF_ESC, 0},
|
|
{0x1800, 0xfc00, 7, 1, 2},
|
|
{0x1c00, 0xfc00, 7, 5, 1},
|
|
{0x1400, 0xfc00, 7, 6, 1},
|
|
{0x1000, 0xfc00, 7, 7, 1},
|
|
{0x0c00, 0xfe00, 8, 0, 4},
|
|
{0x0800, 0xfe00, 8, 2, 2},
|
|
{0x0e00, 0xfe00, 8, 8, 1},
|
|
{0x0a00, 0xfe00, 8, 9, 1},
|
|
{0x2600, 0xff00, 9, 0, 5},
|
|
{0x2100, 0xff00, 9, 0, 6},
|
|
{0x2500, 0xff00, 9, 1, 3},
|
|
{0x2400, 0xff00, 9, 3, 2},
|
|
{0x2700, 0xff00, 9, 10, 1},
|
|
{0x2300, 0xff00, 9, 11, 1},
|
|
{0x2200, 0xff00, 9, 12, 1},
|
|
{0x2000, 0xff00, 9, 13, 1},
|
|
{0x0280, 0xffc0, 11, 0, 7},
|
|
{0x0300, 0xffc0, 11, 1, 4},
|
|
{0x02c0, 0xffc0, 11, 2, 3},
|
|
{0x03c0, 0xffc0, 11, 4, 2},
|
|
{0x0240, 0xffc0, 11, 5, 2},
|
|
{0x0380, 0xffc0, 11, 14, 1},
|
|
{0x0340, 0xffc0, 11, 15, 1},
|
|
{0x0200, 0xffc0, 11, 16, 1},
|
|
{0x01d0, 0xfff0, 13, 0, 8},
|
|
{0x0180, 0xfff0, 13, 0, 9},
|
|
{0x0130, 0xfff0, 13, 0, 10},
|
|
{0x0100, 0xfff0, 13, 0, 11},
|
|
{0x01b0, 0xfff0, 13, 1, 5},
|
|
{0x0140, 0xfff0, 13, 2, 4},
|
|
{0x01c0, 0xfff0, 13, 3, 3},
|
|
{0x0120, 0xfff0, 13, 4, 3},
|
|
{0x01e0, 0xfff0, 13, 6, 2},
|
|
{0x0150, 0xfff0, 13, 7, 2},
|
|
{0x0110, 0xfff0, 13, 8, 2},
|
|
{0x01f0, 0xfff0, 13, 17, 1},
|
|
{0x01a0, 0xfff0, 13, 18, 1},
|
|
{0x0190, 0xfff0, 13, 19, 1},
|
|
{0x0170, 0xfff0, 13, 20, 1},
|
|
{0x0160, 0xfff0, 13, 21, 1},
|
|
{0x00d0, 0xfff8, 14, 0, 12},
|
|
{0x00c8, 0xfff8, 14, 0, 13},
|
|
{0x00c0, 0xfff8, 14, 0, 14},
|
|
{0x00b8, 0xfff8, 14, 0, 15},
|
|
{0x00b0, 0xfff8, 14, 1, 6},
|
|
{0x00a8, 0xfff8, 14, 1, 7},
|
|
{0x00a0, 0xfff8, 14, 2, 5},
|
|
{0x0098, 0xfff8, 14, 3, 4},
|
|
{0x0090, 0xfff8, 14, 5, 3},
|
|
{0x0088, 0xfff8, 14, 9, 2},
|
|
{0x0080, 0xfff8, 14, 10, 2},
|
|
{0x00f8, 0xfff8, 14, 22, 1},
|
|
{0x00f0, 0xfff8, 14, 23, 1},
|
|
{0x00e8, 0xfff8, 14, 24, 1},
|
|
{0x00e0, 0xfff8, 14, 25, 1},
|
|
{0x00d8, 0xfff8, 14, 26, 1},
|
|
};
|
|
|
|
static ParseReturn
|
|
decode_mba (GstBitReader * br, gint * mba)
|
|
{
|
|
gint i;
|
|
guint16 code;
|
|
|
|
*mba = -1;
|
|
do {
|
|
PEEK_BITS (br, &code, 16);
|
|
for (i = 0; i < MBA_LEN; i++) {
|
|
if ((code & mba_table[i][1]) == mba_table[i][0]) {
|
|
*mba = mba_table[i][3];
|
|
|
|
if (*mba == MBA_START_CODE)
|
|
return PARSE_END_OF_GOB;
|
|
SKIP_BITS (br, mba_table[i][2]);
|
|
if (*mba != MBA_STUFFING)
|
|
return PARSE_OK;
|
|
}
|
|
}
|
|
} while (*mba == MBA_STUFFING);
|
|
|
|
/* 0x0 indicates end of frame since we appended 0-bytes */
|
|
if (code == 0x0)
|
|
return PARSE_END_OF_FRAME;
|
|
|
|
return PARSE_ERROR;
|
|
}
|
|
|
|
static ParseReturn
|
|
decode_mtype (GstBitReader * br, guint * mtype)
|
|
{
|
|
gint i;
|
|
guint16 code;
|
|
|
|
PEEK_BITS (br, &code, 16);
|
|
for (i = 0; i < MTYPE_LEN; i++) {
|
|
if ((code & mtype_table[i][1]) == mtype_table[i][0]) {
|
|
SKIP_BITS (br, mtype_table[i][2]);
|
|
*mtype = mtype_table[i][3];
|
|
return PARSE_OK;
|
|
}
|
|
}
|
|
|
|
return PARSE_ERROR;
|
|
}
|
|
|
|
static ParseReturn
|
|
decode_mvd (GstBitReader * br, gint * mvd1, gint * mvd2)
|
|
{
|
|
gint i;
|
|
guint16 code;
|
|
|
|
PEEK_BITS (br, &code, 16);
|
|
for (i = 0; i < MVD_LEN; i++) {
|
|
if ((code & mvd_table[i][1]) == mvd_table[i][0]) {
|
|
SKIP_BITS (br, mvd_table[i][2]);
|
|
*mvd1 = (gint16) mvd_table[i][3];
|
|
*mvd2 = (gint16) mvd_table[i][4];
|
|
return PARSE_OK;
|
|
}
|
|
}
|
|
|
|
return PARSE_ERROR;
|
|
}
|
|
|
|
static ParseReturn
|
|
decode_cbp (GstBitReader * br, guint * cbp)
|
|
{
|
|
gint i;
|
|
guint16 code;
|
|
|
|
PEEK_BITS (br, &code, 16);
|
|
for (i = 0; i < CBP_LEN; i++) {
|
|
if ((code & cbp_table[i][1]) == cbp_table[i][0]) {
|
|
SKIP_BITS (br, cbp_table[i][2]);
|
|
*cbp = cbp_table[i][3];
|
|
return PARSE_OK;
|
|
}
|
|
}
|
|
|
|
return PARSE_ERROR;
|
|
}
|
|
|
|
static ParseReturn
|
|
decode_tcoeff (GstBitReader * br, guint mtype)
|
|
{
|
|
gint i;
|
|
guint16 code;
|
|
gboolean eob;
|
|
|
|
/* Special handling of first coeff */
|
|
if (mtype & MTYPE_INTER) {
|
|
/* Inter, different vlc since EOB is not allowed */
|
|
PEEK_BITS (br, &code, 16);
|
|
if (code & 0x8000) {
|
|
SKIP_BITS (br, 2);
|
|
GST_TRACE ("tcoeff first inter special");
|
|
} else {
|
|
/* Fallthrough. Let the first coeff be handled like other coeffs since
|
|
* the vlc is the same as long as the first bit is not set. */
|
|
}
|
|
} else {
|
|
/* Intra, first coeff is fixed 8-bit */
|
|
GST_TRACE ("tcoeff first intra special");
|
|
SKIP_BITS (br, 8);
|
|
}
|
|
|
|
/* Block must end with EOB. */
|
|
eob = FALSE;
|
|
while (!eob) {
|
|
PEEK_BITS (br, &code, 16);
|
|
for (i = 0; i < TCOEFF_LEN; i++) {
|
|
if ((code & tcoeff_table[i][1]) == tcoeff_table[i][0]) {
|
|
GST_TRACE ("tcoeff vlc[%d], run=%d, level=%d", i, tcoeff_table[i][3],
|
|
tcoeff_table[i][4]);
|
|
SKIP_BITS (br, tcoeff_table[i][2]);
|
|
if (tcoeff_table[i][3] == TCOEFF_EOB) {
|
|
eob = TRUE;
|
|
} else if (tcoeff_table[i][3] == TCOEFF_ESC) {
|
|
#if 0
|
|
guint16 val;
|
|
val = gst_bit_reader_peek_bits_uint16_unchecked (br, 6 + 8);
|
|
GST_TRACE ("esc run=%d, level=%d", val >> 8, (gint8) (val & 0xff));
|
|
#endif
|
|
SKIP_BITS (br, 6 + 8);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (i == TCOEFF_LEN)
|
|
/* No matching VLC */
|
|
return PARSE_ERROR;
|
|
}
|
|
|
|
return PARSE_OK;
|
|
}
|
|
|
|
static gint
|
|
find_picture_header_offset (const guint8 * data, gsize size)
|
|
{
|
|
gint i;
|
|
guint32 val;
|
|
|
|
if (size < 4)
|
|
return -1;
|
|
|
|
val = GST_READ_UINT32_BE (data);
|
|
for (i = 0; i < 8; i++) {
|
|
if ((val >> (12 - i)) == 0x10)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static ParseReturn
|
|
parse_picture_header (GstRtpH261Pay * pay, GstBitReader * br, gint * num_gob)
|
|
{
|
|
guint32 val;
|
|
|
|
GET_BITS (br, &val, PSC_LEN);
|
|
if (val != 0x10)
|
|
return PARSE_ERROR;
|
|
SKIP_BITS (br, TR_LEN);
|
|
GET_BITS (br, &val, PTYPE_LEN);
|
|
*num_gob = (val & 0x04) == 0 ? 3 : 12;
|
|
|
|
return PARSE_OK;
|
|
}
|
|
|
|
static ParseReturn
|
|
parse_gob_header (GstRtpH261Pay * pay, GstBitReader * br, Gob * gob)
|
|
{
|
|
guint32 val;
|
|
|
|
GET_BITS (br, &val, GBSC_LEN);
|
|
if (val != 0x01)
|
|
return PARSE_ERROR;
|
|
GET_BITS (br, &gob->gn, GN_LEN);
|
|
GST_TRACE_OBJECT (pay, "Parsing GOB %d", gob->gn);
|
|
|
|
GET_BITS (br, &gob->gquant, GQUANT_LEN);
|
|
GST_TRACE_OBJECT (pay, "GQUANT %d", gob->gquant);
|
|
GET_BITS (br, &val, GEI_LEN);
|
|
while (val != 0) {
|
|
SKIP_BITS (br, GSPARE_LEN);
|
|
GET_BITS (br, &val, GEI_LEN);
|
|
}
|
|
|
|
return PARSE_OK;
|
|
}
|
|
|
|
static ParseReturn
|
|
parse_mb (GstRtpH261Pay * pay, GstBitReader * br, const Macroblock * prev,
|
|
Macroblock * mb)
|
|
{
|
|
gint mba_diff;
|
|
guint cbp;
|
|
ParseReturn ret;
|
|
|
|
cbp = 0x3f;
|
|
mb->quant = prev->quant;
|
|
|
|
if ((ret = decode_mba (br, &mba_diff)) != PARSE_OK)
|
|
return ret;
|
|
mb->mba = prev->mba == 0 ? mba_diff : prev->mba + mba_diff;
|
|
GST_TRACE_OBJECT (pay, "Parse MBA %d (mba_diff %d)", mb->mba, mba_diff);
|
|
|
|
if ((ret = decode_mtype (br, &mb->mtype)) != PARSE_OK)
|
|
return ret;
|
|
GST_TRACE_OBJECT (pay,
|
|
"MTYPE: inter %d, mc %d, fil %d, mquant %d, mvd %d, cbp %d, tcoeff %d",
|
|
(mb->mtype & MTYPE_INTER) != 0, (mb->mtype & MTYPE_MC) != 0,
|
|
(mb->mtype & MTYPE_FIL) != 0, (mb->mtype & MTYPE_MQUANT) != 0,
|
|
(mb->mtype & MTYPE_MVD) != 0, (mb->mtype & MTYPE_CBP) != 0,
|
|
(mb->mtype & MTYPE_TCOEFF) != 0);
|
|
|
|
if (mb->mtype & MTYPE_MQUANT) {
|
|
GET_BITS (br, &mb->quant, MQUANT_LEN);
|
|
GST_TRACE_OBJECT (pay, "MQUANT: %d", mb->quant);
|
|
}
|
|
|
|
if (mb->mtype & MTYPE_MVD) {
|
|
gint i, pmv[2], mv[2];
|
|
|
|
if (mb->mba == 1 || mb->mba == 12 || mb->mba == 23 || mba_diff != 1 ||
|
|
(prev->mtype & MTYPE_INTER) == 0) {
|
|
pmv[0] = 0;
|
|
pmv[1] = 0;
|
|
} else {
|
|
pmv[0] = prev->mvx;
|
|
pmv[1] = prev->mvy;
|
|
}
|
|
for (i = 0; i < 2; i++) {
|
|
gint mvd1, mvd2;
|
|
if ((ret = decode_mvd (br, &mvd1, &mvd2)) != PARSE_OK)
|
|
return ret;
|
|
if (ABS (pmv[i] + mvd1) <= 15)
|
|
mv[i] = pmv[i] + mvd1;
|
|
else
|
|
mv[i] = pmv[i] + mvd2;
|
|
}
|
|
mb->mvx = mv[0];
|
|
mb->mvy = mv[1];
|
|
} else {
|
|
mb->mvx = 0;
|
|
mb->mvy = 0;
|
|
}
|
|
|
|
if (mb->mtype & MTYPE_CBP) {
|
|
if ((ret = decode_cbp (br, &cbp)) != PARSE_OK)
|
|
return ret;
|
|
}
|
|
|
|
/* Block layer */
|
|
if (mb->mtype & MTYPE_TCOEFF) {
|
|
gint block;
|
|
for (block = 0; block < 6; block++) {
|
|
if (cbp & (1 << (5 - block))) {
|
|
GST_TRACE_OBJECT (pay, "Decode TCOEFF for block %d", block);
|
|
if ((ret = decode_tcoeff (br, mb->mtype)) != PARSE_OK)
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
mb->endpos = gst_bit_reader_get_pos (br);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Parse macroblocks until the next MB that exceeds maxpos. At least one MB is
|
|
* included even if it exceeds maxpos. Returns endpos of last included MB. */
|
|
static ParseReturn
|
|
parse_mb_until_pos (GstRtpH261Pay * pay, GstBitReader * br, Gob * gob,
|
|
guint * endpos)
|
|
{
|
|
ParseReturn ret;
|
|
gint count = 0;
|
|
gboolean stop = FALSE;
|
|
guint maxpos = *endpos;
|
|
Macroblock mb;
|
|
|
|
GST_LOG_OBJECT (pay, "Parse until pos %u, start at pos %u, gobn %d, mba %d",
|
|
maxpos, gst_bit_reader_get_pos (br), gob->gn, gob->last.mba);
|
|
|
|
while (!stop) {
|
|
ret = parse_mb (pay, br, &gob->last, &mb);
|
|
|
|
switch (ret) {
|
|
case PARSE_OK:
|
|
if (mb.endpos > maxpos && count > 0) {
|
|
/* Don't include current MB */
|
|
stop = TRUE;
|
|
} else {
|
|
/* Update to include current MB */
|
|
*endpos = mb.endpos;
|
|
gob->last = mb;
|
|
count++;
|
|
}
|
|
break;
|
|
|
|
case PARSE_END_OF_FRAME:
|
|
*endpos = gst_bit_reader_get_pos (br);
|
|
GST_DEBUG_OBJECT (pay, "End of frame at pos %u (last GOBN %d MBA %d)",
|
|
*endpos, gob->gn, gob->last.mba);
|
|
stop = TRUE;
|
|
break;
|
|
|
|
case PARSE_END_OF_GOB:
|
|
/* Note that a GOB can contain nothing, so we may get here on the first
|
|
* iteration. */
|
|
*endpos = gob->last.mba == 0 ?
|
|
gob->startpos : gst_bit_reader_get_pos (br);
|
|
GST_DEBUG_OBJECT (pay, "End of gob at pos %u (last GOBN %d MBA %d)",
|
|
*endpos, gob->gn, gob->last.mba);
|
|
stop = TRUE;
|
|
break;
|
|
|
|
case PARSE_END_OF_BUFFER:
|
|
case PARSE_ERROR:
|
|
GST_WARNING_OBJECT (pay, "Failed to parse stream (reason %d)", ret);
|
|
return ret;
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
gob->last.gobn = gob->gn;
|
|
|
|
if (ret == PARSE_OK) {
|
|
GST_DEBUG_OBJECT (pay,
|
|
"Split GOBN %d after MBA %d (endpos %u, maxpos %u, nextpos %u)",
|
|
gob->gn, gob->last.mba, *endpos, maxpos, mb.endpos);
|
|
gst_bit_reader_set_pos (br, *endpos);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static guint
|
|
bitrange_to_bytes (guint first, guint last)
|
|
{
|
|
return (GST_ROUND_UP_8 (last) - GST_ROUND_DOWN_8 (first)) / 8;
|
|
}
|
|
|
|
/* Find next 16-bit GOB start code (0x0001), which may not be byte aligned.
|
|
* Returns the bit offset of the first bit of GBSC. */
|
|
static gssize
|
|
find_gob (GstRtpH261Pay * pay, const guint8 * data, guint size, guint pos)
|
|
{
|
|
gssize ret = -1;
|
|
guint offset;
|
|
|
|
GST_LOG_OBJECT (pay, "Search for GOB from pos %u", pos);
|
|
|
|
for (offset = pos / 8; offset < size - 1; offset++) {
|
|
if (data[offset] == 0x0) {
|
|
gint msb = g_bit_nth_msf (data[offset + 1], 8);
|
|
gint lsb = offset > 0 ? g_bit_nth_lsf (data[offset - 1], -1) : 0;
|
|
if (lsb == -1)
|
|
lsb = 8;
|
|
if (msb >= 0 && lsb >= msb) {
|
|
ret = offset * 8 - msb;
|
|
GST_LOG_OBJECT (pay, "Found GOB start code at bitpos %"
|
|
G_GSSIZE_FORMAT " (%02x %02x %02x)", ret,
|
|
offset > 0 ? data[offset - 1] : 0, data[offset], data[offset + 1]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Scans after all GOB start codes and initalizes the GOB structure with start
|
|
* and end positions. */
|
|
static ParseReturn
|
|
gst_rtp_h261_pay_init_gobs (GstRtpH261Pay * pay, Gob * gobs, gint num_gobs,
|
|
const guint8 * bits, gint len, guint pos)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < num_gobs; i++) {
|
|
gssize gobpos = find_gob (pay, bits, len, pos);
|
|
if (gobpos == -1) {
|
|
GST_WARNING_OBJECT (pay, "Found only %d of %d GOBs", i, num_gobs);
|
|
return PARSE_ERROR;
|
|
}
|
|
GST_LOG_OBJECT (pay, "Found GOB %d at pos %" G_GSSIZE_FORMAT, i, gobpos);
|
|
pos = gobpos + GBSC_LEN;
|
|
|
|
gobs[i].startpos = gobpos;
|
|
if (i > 0)
|
|
gobs[i - 1].endpos = gobpos;
|
|
}
|
|
gobs[num_gobs - 1].endpos = len * 8;
|
|
|
|
return PARSE_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_rtp_h261_pay_fragment_push (GstRtpH261Pay * pay, GstBuffer * buffer,
|
|
const guint8 * bits, guint start, guint end,
|
|
const Macroblock * last_mb_in_previous_packet, gboolean marker)
|
|
{
|
|
GstBuffer *outbuf;
|
|
guint8 *payload;
|
|
GstRtpH261PayHeader *header;
|
|
gint nbytes;
|
|
const Macroblock *last = last_mb_in_previous_packet;
|
|
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
|
|
|
|
nbytes = bitrange_to_bytes (start, end);
|
|
|
|
outbuf = gst_rtp_buffer_new_allocate (nbytes +
|
|
GST_RTP_H261_PAYLOAD_HEADER_LEN, 0, 0);
|
|
gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
|
|
payload = gst_rtp_buffer_get_payload (&rtp);
|
|
header = (GstRtpH261PayHeader *) payload;
|
|
|
|
memset (header, 0, GST_RTP_H261_PAYLOAD_HEADER_LEN);
|
|
header->v = 1;
|
|
header->sbit = start & 7;
|
|
header->ebit = (8 - (end & 7)) & 7;
|
|
|
|
if (last != NULL && last->mba != 0 && last->mba != 33) {
|
|
/* NOTE: MVD assumes that we're running on 2's complement architecture */
|
|
guint mbap = last->mba - 1;
|
|
header->gobn = last->gobn;
|
|
header->mbap1 = mbap >> 1;
|
|
header->mbap2 = mbap & 1;
|
|
header->quant = last->quant;
|
|
header->hmvd1 = last->mvx >> 3;
|
|
header->hmvd2 = last->mvx & 7;
|
|
header->vmvd = last->mvy;
|
|
}
|
|
|
|
memcpy (payload + GST_RTP_H261_PAYLOAD_HEADER_LEN,
|
|
bits + GST_ROUND_DOWN_8 (start) / 8, nbytes);
|
|
|
|
GST_BUFFER_TIMESTAMP (outbuf) = pay->timestamp;
|
|
gst_rtp_buffer_set_marker (&rtp, marker);
|
|
pay->offset = end & 7;
|
|
|
|
GST_DEBUG_OBJECT (pay,
|
|
"Push fragment, bytes %d, sbit %d, ebit %d, gobn %d, mbap %d, marker %d",
|
|
nbytes, header->sbit, header->ebit, last != NULL ? last->gobn : 0,
|
|
last != NULL ? MAX (last->mba - 1, 0) : 0, marker);
|
|
|
|
gst_rtp_buffer_unmap (&rtp);
|
|
|
|
gst_rtp_copy_video_meta (pay, outbuf, buffer);
|
|
|
|
return gst_rtp_base_payload_push (GST_RTP_BASE_PAYLOAD_CAST (pay), outbuf);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_rtp_h261_packetize_and_push (GstRtpH261Pay * pay, GstBuffer * buffer,
|
|
const guint8 * bits, gsize len)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstBitReader br_;
|
|
GstBitReader *br = &br_;
|
|
guint max_payload_size =
|
|
gst_rtp_buffer_calc_payload_len (GST_RTP_BASE_PAYLOAD_MTU (pay) -
|
|
GST_RTP_H261_PAYLOAD_HEADER_LEN, 0, 0);
|
|
guint startpos;
|
|
gint num_gobs = 0;
|
|
Gob gobs[MAX_NUM_GOB];
|
|
Gob *gob;
|
|
Macroblock last_mb_in_previous_packet = { 0 };
|
|
gboolean marker;
|
|
ParseReturn result;
|
|
|
|
gst_bit_reader_init (br, bits, len);
|
|
gst_bit_reader_set_pos (br, pay->offset);
|
|
startpos = pay->offset;
|
|
|
|
if (parse_picture_header (pay, br, &num_gobs) < PARSE_OK) {
|
|
GST_WARNING_OBJECT (pay, "Failed to parse picture header");
|
|
goto beach;
|
|
}
|
|
|
|
if (gst_rtp_h261_pay_init_gobs (pay, gobs, num_gobs, bits, len,
|
|
gst_bit_reader_get_pos (br)) < PARSE_OK)
|
|
goto beach;
|
|
|
|
/* Split, create and push packets */
|
|
gob = gobs;
|
|
marker = FALSE;
|
|
while (marker == FALSE && ret == GST_FLOW_OK) {
|
|
guint endpos;
|
|
|
|
/* Check if there is wrap around because of extremely high MTU */
|
|
endpos = GST_ROUND_DOWN_8 (startpos) + max_payload_size * 8;
|
|
if (endpos < startpos)
|
|
endpos = G_MAXUINT;
|
|
|
|
GST_LOG_OBJECT (pay, "Next packet startpos %u maxpos %u", startpos, endpos);
|
|
|
|
/* Find the last GOB that does not completely fit in packet */
|
|
for (; gob < &gobs[num_gobs - 1]; gob++) {
|
|
if (bitrange_to_bytes (startpos, gob->endpos) > max_payload_size) {
|
|
GST_LOG_OBJECT (pay, "Split gob (start %u, end %u)",
|
|
gob->startpos, gob->endpos);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (startpos <= gob->startpos) {
|
|
/* Fast-forward until start of GOB */
|
|
gst_bit_reader_set_pos (br, gob->startpos);
|
|
if (parse_gob_header (pay, br, gob) < PARSE_OK) {
|
|
GST_WARNING_OBJECT (pay, "Failed to parse GOB header");
|
|
goto beach;
|
|
}
|
|
gob->last.mba = 0;
|
|
gob->last.gobn = gob->gn;
|
|
gob->last.quant = gob->gquant;
|
|
}
|
|
|
|
/* Parse MBs to find position where to split. Can only be done on after MB
|
|
* or at GOB boundary. */
|
|
result = parse_mb_until_pos (pay, br, gob, &endpos);
|
|
if (result < PARSE_OK)
|
|
goto beach;
|
|
|
|
marker = result == PARSE_END_OF_FRAME;
|
|
ret = gst_rtp_h261_pay_fragment_push (pay, buffer, bits, startpos, endpos,
|
|
&last_mb_in_previous_packet, marker);
|
|
|
|
last_mb_in_previous_packet = gob->last;
|
|
if (endpos == gob->endpos)
|
|
gob++;
|
|
startpos = endpos;
|
|
}
|
|
|
|
beach:
|
|
return ret;
|
|
}
|
|
|
|
/* Shift buffer to packetize a continuous stream of bits (not bytes). Some
|
|
* payloaders/decoders are very picky about correct sbit/ebit for frames. */
|
|
static guint8 *
|
|
gst_rtp_h261_pay_shift_buffer (GstRtpH261Pay * pay, const guint8 * data,
|
|
gsize size, gint offset, gsize * newsize)
|
|
{
|
|
/* In order to read variable length codes at the very end of the buffer
|
|
* wihout peeking into possibly unallocated data, we pad with extra 0's
|
|
* which will generate an invalid code at the end of the buffer. */
|
|
guint pad = 4;
|
|
gsize allocsize = size + pad;
|
|
guint8 *bits = g_malloc (allocsize);
|
|
gint i;
|
|
|
|
if (offset == 0) {
|
|
memcpy (bits, data, size);
|
|
*newsize = size;
|
|
} else if (offset > 0) {
|
|
bits[0] = 0;
|
|
for (i = 0; i < size; i++) {
|
|
bits[i] |= data[i] >> offset;
|
|
bits[i + 1] = data[i] << (8 - offset);
|
|
}
|
|
*newsize = size + 1;
|
|
} else {
|
|
gint shift = -offset;
|
|
for (i = 0; i < size - 1; i++)
|
|
bits[i] = (data[i] << shift) | (data[i + 1] >> (8 - shift));
|
|
bits[i] = data[i] << shift;
|
|
*newsize = size;
|
|
}
|
|
for (i = *newsize; i < allocsize; i++)
|
|
bits[i] = 0;
|
|
|
|
return bits;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_rtp_h261_pay_handle_buffer (GstRTPBasePayload * payload, GstBuffer * buffer)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstRtpH261Pay *pay = GST_RTP_H261_PAY (payload);
|
|
gsize len;
|
|
guint8 *bits;
|
|
gint psc_offset, shift;
|
|
GstMapInfo map;
|
|
|
|
GST_DEBUG_OBJECT (pay, "Handle buffer of size %" G_GSIZE_FORMAT,
|
|
gst_buffer_get_size (buffer));
|
|
|
|
pay->timestamp = GST_BUFFER_TIMESTAMP (buffer);
|
|
|
|
if (!gst_buffer_map (buffer, &map, GST_MAP_READ) || !map.data) {
|
|
GST_WARNING_OBJECT (pay, "Failed to map buffer");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
psc_offset = find_picture_header_offset (map.data, map.size);
|
|
if (psc_offset < 0) {
|
|
GST_WARNING_OBJECT (pay, "Failed to find picture header offset");
|
|
goto beach;
|
|
} else {
|
|
GST_DEBUG_OBJECT (pay, "Picture header offset: %d", psc_offset);
|
|
}
|
|
|
|
shift = pay->offset - psc_offset;
|
|
bits = gst_rtp_h261_pay_shift_buffer (pay, map.data, map.size, shift, &len);
|
|
ret = gst_rtp_h261_packetize_and_push (pay, buffer, bits, len);
|
|
g_free (bits);
|
|
|
|
beach:
|
|
gst_buffer_unmap (buffer, &map);
|
|
gst_buffer_unref (buffer);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gst_rtp_h261_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps)
|
|
{
|
|
gboolean res;
|
|
|
|
gst_rtp_base_payload_set_options (payload, "video",
|
|
payload->pt != GST_RTP_PAYLOAD_H261, "H261", 90000);
|
|
res = gst_rtp_base_payload_set_outcaps (payload, NULL);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
gst_rtp_h261_pay_init (GstRtpH261Pay * pay)
|
|
{
|
|
GstRTPBasePayload *payload = GST_RTP_BASE_PAYLOAD (pay);
|
|
payload->pt = GST_RTP_PAYLOAD_H261;
|
|
pay->offset = 0;
|
|
}
|
|
|
|
static void
|
|
gst_rtp_h261_pay_class_init (GstRtpH261PayClass * klass)
|
|
{
|
|
GstElementClass *element_class;
|
|
GstRTPBasePayloadClass *gstrtpbasepayload_class;
|
|
|
|
element_class = GST_ELEMENT_CLASS (klass);
|
|
gstrtpbasepayload_class = GST_RTP_BASE_PAYLOAD_CLASS (klass);
|
|
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&gst_rtp_h261_pay_src_template);
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&gst_rtp_h261_pay_sink_template);
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"RTP H261 packet payloader", "Codec/Payloader/Network/RTP",
|
|
"Payload-encodes H261 video in RTP packets (RFC 4587)",
|
|
"Stian Selnes <stian@pexip.com>");
|
|
|
|
gstrtpbasepayload_class->set_caps = gst_rtp_h261_pay_setcaps;
|
|
gstrtpbasepayload_class->handle_buffer = gst_rtp_h261_pay_handle_buffer;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (rtph261pay_debug, "rtph261pay", 0,
|
|
"H261 RTP Payloader");
|
|
}
|
|
|
|
gboolean
|
|
gst_rtp_h261_pay_plugin_init (GstPlugin * plugin)
|
|
{
|
|
return gst_element_register (plugin, "rtph261pay",
|
|
GST_RANK_SECONDARY, GST_TYPE_RTP_H261_PAY);
|
|
}
|