[RFC,WIP,v4,09/11] media: vidtv: implement a PES packetizer
Commit Message
From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
Implement the PES logic to convert encoder data into MPEG TS packets.
These TS packets can then be fed into a TS multiplexer and eventually
into userspace.
Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
---
drivers/media/test-drivers/vidtv/Makefile | 2 +-
.../media/test-drivers/vidtv/vidtv_common.c | 7 +
.../media/test-drivers/vidtv/vidtv_common.h | 2 +
drivers/media/test-drivers/vidtv/vidtv_pes.c | 429 ++++++++++++++++++
drivers/media/test-drivers/vidtv/vidtv_pes.h | 185 ++++++++
5 files changed, 624 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.h
Comments
Em Sat, 2 May 2020 00:22:14 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:
> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
>
> Implement the PES logic to convert encoder data into MPEG TS packets.
> These TS packets can then be fed into a TS multiplexer and eventually
> into userspace.
>
> Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> .../media/test-drivers/vidtv/vidtv_common.c | 7 +
> .../media/test-drivers/vidtv/vidtv_common.h | 2 +
> drivers/media/test-drivers/vidtv/vidtv_pes.c | 429 ++++++++++++++++++
> drivers/media/test-drivers/vidtv/vidtv_pes.h | 185 ++++++++
> 5 files changed, 624 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index e4f744aa53136..e3a6540f50e87 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,6 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
>
> vidtv_demod-objs := vidtv_common.o
> -vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o
> +vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o
>
> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.c b/drivers/media/test-drivers/vidtv/vidtv_common.c
> index 28f10630499a9..b1412b497e1e3 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_common.c
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.c
> @@ -42,3 +42,10 @@ u32 vidtv_memset(void *to,
> memset(to, c, len);
> return len;
> }
> +
> +u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits)
> +{
> + u64 mask = ((1 << nbits) - 1) << start_bit;
> +
> + return value & mask;
> +}
There are macros already for it, like:
include/linux/bits.h:#define BIT_MASK(nr) (UL(1) << ((nr) % BITS_PER_LONG))
So, please stick with the existing code there, as defining driver-specific
stuff like that is usually painful and may lead into errors.
(Btw, your "mask" definition doesn't work with u64)
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
> index 64072c010dc66..3b68f95c5f6c8 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_common.h
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
> @@ -25,4 +25,6 @@ u32 vidtv_memset(void *to,
> u32 offset,
> u32 buf_sz);
>
> +u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits);
> +
> #endif // VIDTV_COMMON_H
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.c b/drivers/media/test-drivers/vidtv/vidtv_pes.c
> new file mode 100644
> index 0000000000000..bc631bac07778
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_pes.c
> @@ -0,0 +1,429 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the logic to translate the ES data for one access unit
> + * from an encoder into MPEG TS packets. It does so by first encapsulating it
> + * with a PES header and then splitting it into TS packets.
> + *
> + * Written by Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> + */
> +
> +#include <linux/types.h>
> +#include <linux/printk.h>
> +#include "vidtv_pes.h"
> +#include "vidtv_common.h"
> +#include "vidtv_ts.h"
> +
> +#define PRIVATE_STREAM_1_ID 0xbd /* private_stream_1. See SMPTE 302M-2007 p.6 */
> +#define PES_HEADER_MAX_STUFFING_BYTES 32
> +#define PES_TS_HEADER_MAX_STUFFING_BYTES 182
> +
> +static u32 vidtv_pes_op_get_regular_len(bool send_pts, bool send_dts)
> +{
> + u32 len = 0;
> +
> + /* the flags must always be sent */
> + len += sizeof(struct vidtv_pes_optional);
> +
> + /* From all optionals, we might send these for now */
> + if (send_pts && send_dts)
> + len += sizeof(struct vidtv_pes_optional_pts_dts);
> + else if (send_pts)
> + len += sizeof(struct vidtv_pes_optional_pts);
> +
> + return len;
> +}
> +
> +static u32 vidtv_pes_h_get_regular_len(bool send_pts, bool send_dts)
> +{
> + /* PES header length notwithstanding stuffing bytes */
> + u32 len = 0;
> +
> + len += sizeof(struct vidtv_mpeg_pes);
> + len += vidtv_pes_op_get_regular_len(send_pts, send_dts);
> +
> + return len;
> +}
> +
> +static u32 vidtv_pes_write_header_stuffing(struct vidtv_mpeg_pes *pes_h,
> + struct pes_header_write_args args)
> +{
> + /*
> + * This is a fixed 8-bit value equal to '1111 1111' that can be inserted
> + * by the encoder, for example to meet the requirements of the channel.
> + * It is discarded by the decoder. No more than 32 stuffing bytes shall
> + * be present in one PES packet header.
> + */
> +
> + WARN_ON(args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES);
As commented, don't use WARN_ON(). At most, you could use WARN_ON_ONCE(),
as otherwise, you may end by causing serious performance issues if
the code starts to produce a flood of warnings at the dmesg.
I would use pr_warn_ratelimit() on all such cases.
> +
> + if (args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES)
> + args.n_pes_h_s_bytes = PES_HEADER_MAX_STUFFING_BYTES;
> +
> + /* gives the length of the remainder of the PES header in bytes */
> + pes_h->length += args.n_pes_h_s_bytes;
> +
> + return vidtv_memset(args.dest_buf + args.dest_offset,
> + 0xff,
> + args.n_pes_h_s_bytes,
> + args.dest_offset,
> + args.dest_buf_sz);
> +}
> +
> +static u32 vidtv_pes_write_pts_dts(struct pes_header_write_args args)
> +{
> + u32 nbytes = 0; /* the number of bytes written by this function */
> +
> + struct vidtv_pes_optional_pts pts = {0};
> + struct vidtv_pes_optional_pts_dts pts_dts = {0};
> + void *op = NULL;
> + size_t op_sz = 0;
> +
> + if (!args.send_pts && args.send_dts)
> + return 0;
> +
> + /* see ISO/IEC 13818-1 : 2000 p. 32 */
> +
> + if (args.send_pts && args.send_dts) {
> + cpu_to_be64s(&args.pts);
> + cpu_to_be64s(&args.dts);
> + pts_dts.three = 0x3;
> +
> + pts_dts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
> + pts_dts.marker1 = 0x1;
> + pts_dts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
> + pts_dts.marker2 = 0x1;
> + pts_dts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
> + pts_dts.marker3 = 0x1;
> +
> + pts_dts.one = 0x1;
> +
> + pts_dts.dts1 = vidtv_extract_bits(args.dts, 30, 3);
> + pts_dts.marker1 = 0x1;
> + pts_dts.dts2 = vidtv_extract_bits(args.dts, 15, 15);
> + pts_dts.marker2 = 0x1;
> + pts_dts.dts3 = vidtv_extract_bits(args.dts, 0, 15);
> + pts_dts.marker3 = 0x1;
> +
> + be64_to_cpus(&args.pts);
> + be64_to_cpus(&args.dts);
> +
> + op = &pts_dts;
> + op_sz = sizeof(pts_dts);
> +
> + } else if (args.send_pts) {
> + cpu_to_be64s(&args.pts);
> + pts.two = 0x2;
> + pts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
> + pts.marker1 = 0x1;
> + pts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
> + pts.marker2 = 0x1;
> + pts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
> + pts.marker3 = 0x1;
> + be64_to_cpus(&args.pts);
> +
> + op = &pts;
> + op_sz = sizeof(pts);
> + }
> +
> + /* copy PTS/DTS optional */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + op,
> + op_sz,
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_pes_write_h(struct pes_header_write_args args)
> +{
> + u32 nbytes = 0; /* the number of bytes written by this function */
> +
> + struct vidtv_mpeg_pes pes_header = {0};
> + struct vidtv_pes_optional pes_optional = {0};
> + struct pes_header_write_args pts_dts_args = args;
> +
> + pes_header.packet_start_code_prefix = PES_START_CODE_PREFIX;
> +
> + pes_header.stream_id = (args.is_s302m_pkt) ?
> + PRIVATE_STREAM_1_ID :
> + args.stream_id;
> +
> + pes_header.length = vidtv_pes_op_get_regular_len(args.send_pts,
> + args.send_dts);
> +
> + pes_optional.two = 0x2;
> +
> + pes_optional.PTS_DTS = (args.send_pts && args.send_dts) ?
> + 0x3 :
> + (args.send_pts) ?
> + 0x2 :
> + 0x0;
> +
> + /* copy header */
> + cpu_to_be32s(&pes_header.bitfield);
> + cpu_to_be16s(&pes_header.length);
> +
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &pes_header,
> + sizeof(pes_header),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + be32_to_cpus(&pes_header.bitfield);
> + be16_to_cpus(&pes_header.length);
I don't like the idea of changing the "from" buffer endiannes, copy
and then restore it back to the original state. Is this really needed?
I would, instead, define:
struct pes_header {
...
__be32 bitfield;
__be16 length;
...
};
Then wherever you would touch them:
u32 bitfield;
u16 len;
/* Write into BE fields */
pes_header.bitfield = cpu_to_be32(bitfield);
pes_header.length = cpu_to_be16(len);
/* Read from BE fields */
bitfield = be32_to_cpu(pes_header.bitfield);
len = be16_to_cpu(pes_header.length);
As a side effect, when you use "__be16" and "__be32" types, gcc
and smatch/sparse will warn you if you mess with endiannes.
Same applies to similar code elsewhere.
> +
> + /* copy optional header */
> + cpu_to_be16s(&pes_optional.bitfield);
> +
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &pes_optional,
> + sizeof(pes_optional),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + be16_to_cpus(&pes_optional.bitfield);
> +
> + pts_dts_args.dest_offset = args.dest_offset + nbytes;
> + nbytes += vidtv_pes_write_pts_dts(pts_dts_args);
> +
> + /* write any PES header stuffing */
> + nbytes += vidtv_pes_write_header_stuffing(&pes_header, args);
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_pes_write_stuffing(struct vidtv_mpeg_ts *ts_h,
> + void *dest_buf,
> + u32 dest_offset,
> + u32 n_stuffing_bytes,
> + u32 buf_sz)
> +{
> + /*
> + * For Transport Stream packets carrying PES packets, stuffing is
> + * needed when there is insufficient PES packet data to completely
> + * fill the Transport Stream packet payload bytes. Stuffing is
> + * accomplished by defining an adaptation field longer than the sum of
> + * the lengths of the data elements in it, so that the payload bytes
> + * remaining after the adaptation field exactly accommodates the
> + * available PES packet data. The extra space in the adaptation field
> + * is filled with stuffing bytes.
> + *
> + */
> +
> + /* the number of bytes written by this function */
> + u32 nbytes = 0;
> + struct vidtv_mpeg_ts_adaption ts_adap = {0};
> +
> + if (!n_stuffing_bytes)
> + return nbytes;
> +
> + ts_h->adaptation_field = 1;
> +
> + WARN_ON(n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES);
> +
> + if (n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES)
> + n_stuffing_bytes = PES_TS_HEADER_MAX_STUFFING_BYTES;
> +
> + /* the AF will only be its 'length' field with a value of zero */
> + if (n_stuffing_bytes == 1) {
> + nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
> + 0,
> + n_stuffing_bytes,
> + dest_offset + nbytes,
> + buf_sz);
> +
> + return nbytes;
> + }
> +
> + n_stuffing_bytes -= sizeof(ts_adap);
> +
> + /* length _immediately_ following 'adaptation_field_length' */
> + ts_adap.length = sizeof(ts_adap) -
> + sizeof(ts_adap.length) +
> + n_stuffing_bytes;
> +
> + /* write the adap after the TS header */
> + nbytes += vidtv_memcpy(dest_buf + dest_offset + nbytes,
> + &ts_adap,
> + sizeof(ts_adap),
> + dest_offset + nbytes,
> + buf_sz);
> +
> + /* write the stuffing bytes */
> + nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
> + 0xff,
> + n_stuffing_bytes,
> + dest_offset + nbytes,
> + buf_sz);
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_pes_write_ts_h(struct pes_ts_header_write_args args)
> +{
> + /* number of bytes written by this function */
> + u32 nbytes = 0;
> + struct vidtv_mpeg_ts ts_header = {0};
> +
> + ts_header.sync_byte = TS_SYNC_BYTE;
> + ts_header.tei = 0;
> + ts_header.pid = args.pid;
> + ts_header.priority = 0;
> + ts_header.scrambling = 0; /* not scrambled */
> + ts_header.adaptation_field = 0;
> + ts_header.payload = 1;
> +
> + ts_header.payload_start = (!args.wrote_pes_header) ? 1 : 0;
> + ts_header.continuity_counter = *args.continuity_counter;
> +
> + vidtv_ts_inc_cc(args.continuity_counter);
> +
> + cpu_to_be16s(&ts_header.bitfield);
> +
> + /* write the TS header */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &ts_header,
> + sizeof(ts_header),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + be16_to_cpus(&ts_header.bitfield);
> + /* write stuffing, if any */
> + nbytes += vidtv_pes_write_stuffing(&ts_header,
> + args.dest_buf,
> + args.dest_offset + nbytes,
> + args.n_stuffing_bytes,
> + args.dest_buf_sz);
> +
> + return nbytes;
> +}
> +
> +u32 vidtv_pes_write_into(struct pes_write_args args)
> +{
> + u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN);
> + bool aligned = (nbytes_past_boundary == 0);
> +
> + struct pes_ts_header_write_args ts_header_args = {0};
> + struct pes_header_write_args pes_header_args = {0};
> +
> + /* number of bytes written by this function */
> + u32 nbytes = 0;
> + u32 remaining_len = args.access_unit_len;
> +
> + bool wrote_pes_header = false;
> + bool stuff = false;
> +
> + u32 available_space = 0;
> + u32 payload_write_len = 0;
> + u32 num_stuffing_bytes = 0;
> +
> + /* Just a sanity check, should not really happen because we stuff the
> + * TS packet when we finish writing the PES data, but if this happens
> + * then we have messed up the logic somewhere.
> + *
> + * Also note that, unlike packets for PSI data, we need to carry PES
> + * packets aligned with the payload of transport packets, that is the
> + * first byte of each PES header must be the first byte in the payload
> + * of a transport packet. As a consequence, the last byte of a PES
> + * packet must be the last byte of the payload of a transport packet.
> + */
> + WARN_ON(!aligned);
Same note as on a previous patch: if not aligned, you should re-align.
> +
> + if (args.send_dts && !args.send_pts) {
> + pr_warn("%s: forbidden value '01' for PTS_DTS flags", __func__);
> + args.send_pts = true;
> + args.pts = args.dts;
> + }
> +
> + /* see SMPTE 302M clause 6.4 */
> + if (args.is_s302m_pkt) {
> + args.send_dts = false;
> + args.send_pts = true;
> + }
> +
> + while (remaining_len) {
> + /*
> + * The amount of space initially available in the TS packet.
> + * if this is the beginning of the PES packet, we need to
> + * take into account the space needed for the TS header _and_
> + * for the PES header
> + */
> + available_space = (!wrote_pes_header) ?
> + TS_PAYLOAD_LEN -
> + vidtv_pes_h_get_regular_len(args.send_pts,
> + args.send_dts) :
> + TS_PAYLOAD_LEN;
> +
> + /* if the encoder has inserted stuffing bytes in the PES
> + * header, account for them.
> + */
> + available_space -= args.n_pes_h_s_bytes;
> +
> + /* whether we need to stuff the TS packet to align the buffer */
> + stuff = remaining_len < available_space;
> +
> + /*
> + * how much of the _actual_ payload we should write in this
> + * packet.
> + */
> + payload_write_len = (stuff) ?
> + remaining_len :
> + available_space;
> +
> + num_stuffing_bytes = available_space - payload_write_len;
> +
> + /* write ts header */
> + ts_header_args.dest_buf = args.dest_buf;
> + ts_header_args.dest_offset = args.dest_offset + nbytes;
> + ts_header_args.dest_buf_sz = args.dest_buf_sz;
> + ts_header_args.pid = args.pid;
> + ts_header_args.continuity_counter = args.continuity_counter;
> + ts_header_args.wrote_pes_header = wrote_pes_header;
> + ts_header_args.n_stuffing_bytes = num_stuffing_bytes;
> +
> + nbytes += vidtv_pes_write_ts_h(ts_header_args);
> +
> + if (!wrote_pes_header) {
> + /* write the PES header only once */
> + pes_header_args.dest_buf = args.dest_buf;
> +
> + pes_header_args.dest_offset = args.dest_offset +
> + nbytes;
> +
> + pes_header_args.dest_buf_sz = args.dest_buf_sz;
> + pes_header_args.is_s302m_pkt = args.is_s302m_pkt;
> + pes_header_args.send_pts = args.send_pts;
> + pes_header_args.pts = args.pts;
> + pes_header_args.send_dts = args.send_dts;
> + pes_header_args.dts = args.dts;
> + pes_header_args.stream_id = args.stream_id;
> + pes_header_args.n_pes_h_s_bytes = args.n_pes_h_s_bytes;
> +
> + nbytes += vidtv_pes_write_h(pes_header_args);
> + wrote_pes_header = true;
> + }
> +
> + /* write as much of the payload as we possibly can */
> + nbytes += vidtv_memcpy(args.dest_buf +
> + args.dest_offset +
> + nbytes,
> + args.from,
> + payload_write_len,
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + args.from += payload_write_len;
> + args.dest_offset += nbytes;
> +
> + /* sanity check for underflow */
> + WARN_ON(remaining_len - payload_write_len > remaining_len);
> + remaining_len -= payload_write_len;
> + }
> +
> + return nbytes;
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.h b/drivers/media/test-drivers/vidtv/vidtv_pes.h
> new file mode 100644
> index 0000000000000..00ede32adf476
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_pes.h
> @@ -0,0 +1,185 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the logic to translate the ES data for one access unit
> + * from an encoder into MPEG TS packets. It does so by first encapsulating it
> + * with a PES header and then splitting it into TS packets.
> + *
> + * Written by Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> + */
> +
> +#ifndef VIDTV_PES_H
> +#define VIDTV_PES_H
> +
> +#include <asm/byteorder.h>
> +#include <linux/types.h>
> +#include "vidtv_common.h"
> +
> +#define PES_MAX_LEN 65536 /* Set 'length' to 0 if greater */
> +#define PES_START_CODE_PREFIX 0x001 /* 00 00 01 */
> +
> +struct vidtv_pes_optional_pts {
> + struct {
> + #if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 marker3:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker1:1; /* always 0x1 */
> + u8 pts1:3;
> + u8 two:4; /* always 0010b */
> + #elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 two:4; /* always 0010b */
> + u8 pts1:3;
> + u8 marker1:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker3:1; /* always 0x1 */
> + #else
> + #error "Please fix <asm/byteorder.h>"
> + #endif
> + } __packed;
> +} __packed;
> +
> +struct vidtv_pes_optional_pts_dts {
> + struct {
> + #if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 marker6:1; /* always 0x1 */
> + u16 dts3:15;
> + u8 marker5:1; /* always 0x1 */
> + u16 dts2:15;
> + u8 marker4:1; /* always 0x1 */
> + u8 dts1:3;
> + u8 one:4; /* always 0001b */
> + u8 marker3:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker1:1; /* always 0x1 */
> + u8 pts1:3;
> + u8 three:4; /* always 0011b */
> + #elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 three:4; /* always 0011b */
> + u8 pts1:3;
> + u8 marker1:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker3:1; /* always 0x1 */
> + u8 one:4; /* always 0001b */
> + u8 dts1:3;
> + u8 marker4:1; /* always 0x1 */
> + u16 dts2:15;
> + u8 marker5:1; /* always 0x1 */
> + u16 dts3:15;
> + u8 marker6:1; /* always 0x1 */
> + #else
> + #error "Please fix <asm/byteorder.h>"
> + #endif
> + } __packed;
> +} __packed;
> +
> +struct vidtv_pes_optional {
> + union {
> + u16 bitfield;
> + struct {
> + u16 two:2; /* always 0x2*/
> + u16 PES_scrambling_control:2;
> + u16 PES_priority:1;
> + u16 data_alignment_indicator:1; /* ununsed for us */
> + u16 copyright:1;
> + u16 original_or_copy:1;
> + u16 PTS_DTS:2;
> + /* These flags show which components are actually
> + * present in the "optinal fields" in the optinal PES
> + * header and which are not. Vidtv currently does
> + * not need any of these.
> + */
> + u16 ESCR:1;
> + u16 ES_rate:1;
> + u16 DSM_trick_mode:1;
> + u16 additional_copy_info:1;
> + u16 PES_CRC:1;
> + u16 PES_extension:1;
> + } __packed;
> + } __packed;
> + u8 length;
> +} __packed;
> +
> +struct vidtv_mpeg_pes {
> + union {
> + u32 bitfield;
> + struct {
> + /* These two together make the 32-bit start-code */
> + u32 packet_start_code_prefix:24;
> + u32 stream_id:8;
> + } __packed;
> + } __packed;
> + /* after this field until the end of the PES data payload */
> + u16 length;
> + struct vidtv_pes_optional optional[];
> +} __packed;
> +
> +struct pes_header_write_args {
> + void *dest_buf;
> + u32 dest_offset;
> + u32 dest_buf_sz;
> + bool is_s302m_pkt;
> +
> + bool send_pts;
> + u64 pts;
> +
> + bool send_dts;
> + u64 dts;
> +
> + u16 stream_id;
> + /* might be used by an encoder if needed, gets discarded by decoder */
> + u32 n_pes_h_s_bytes;
> +};
> +
> +struct pes_ts_header_write_args {
> + void *dest_buf;
> + u32 dest_offset;
> + u32 dest_buf_sz;
> + u16 pid;
> + u8 *continuity_counter;
> + bool wrote_pes_header;
> + u32 n_stuffing_bytes;
> +};
> +
> +struct pes_write_args {
> + void *dest_buf; /* pointer to a program mux buffer */
> + void *from; /* pointer to the encoder buffer */
> +
> + /* the size of one access unit (with any headers it might need) */
> + u32 access_unit_len;
> +
> + u32 dest_offset; /* where to start writing in the program mux buffer */
> + u32 dest_buf_sz; /* how big is the program mux buffer */
> + u16 pid; /* TS packet ID */
> +
> + /* use SMPTE 302M to packetize the data */
> + bool is_s302m_pkt;
> +
> + u8 *continuity_counter; /* incremented for every TS packet */
> +
> + /* Examples: Audio streams (0xc0-0xdf), Video streams (0xe0-0xef) */
> + u16 stream_id;
> +
> + bool send_pts;
> + u64 pts;
> +
> + bool send_dts;
> + u64 dts;
> +
> + /* might be used by an encoder if needed, gets discarded by decoder */
> + u32 n_pes_h_s_bytes;
> +};
> +
> +u32 vidtv_pes_write_into(struct pes_write_args args);
> +
> +#endif // VIDTV_PES_H
Thanks,
Mauro
Hi Mauro,
> As commented, don't use WARN_ON(). At most, you could use WARN_ON_ONCE(),
> as otherwise, you may end by causing serious performance issues if
> the code starts to produce a flood of warnings at the dmesg.
>
> I would use pr_warn_ratelimit() on all such cases.
>
OK.
> I don't like the idea of changing the "from" buffer endiannes, copy
> and then restore it back to the original state. Is this really needed?
>
> I would, instead, define:
>
> struct pes_header {
> ...
> __be32 bitfield;
> __be16 length;
> ...
> };
>
> Then wherever you would touch them:
>
> u32 bitfield;
> u16 len;
>
> /* Write into BE fields */
> pes_header.bitfield = cpu_to_be32(bitfield);
> pes_header.length = cpu_to_be16(len);
>
> /* Read from BE fields */
> bitfield = be32_to_cpu(pes_header.bitfield);
> len = be16_to_cpu(pes_header.length);
>
>
> As a side effect, when you use "__be16" and "__be32" types, gcc
> and smatch/sparse will warn you if you mess with endiannes.
>
> Same applies to similar code elsewhere.
>
I don't like it either, it is error prone. I did not know about this
other possibility. Does this work for _bitfields_ though?
I think the authors for libdvbv5 used unions precisely so bswap() could
be called on a 'bitfield' member and from then on the bitfields could be
accessed directly, e.g.:
union {
u16 bitfield; <-- call bswap() on this
struct {
--> then use these directly:
u8 syntax:1;
u8 zero:1;
u8 one:2;
u16 section_length:12;
} __packed;
} __packed
At least that's what I understood.
I found this:
https://lwn.net/Articles/741762/
Maybe *_get_bits, *_replace_bits is the equivalent that I should use for bitfields?
Because I'd rather not do this:
> u32 bitfield;
> /* Write into BE fields */
> pes_header.bitfield = cpu_to_be32(bitfield);
Since I'd have to write the (many!) bitwise operations myself and I'm
sure I will mess this up at _some_ point.
thanks,
- Daniel
Em Wed, 6 May 2020 03:55:48 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:
> Hi Mauro,
>
>
> > As commented, don't use WARN_ON(). At most, you could use WARN_ON_ONCE(),
> > as otherwise, you may end by causing serious performance issues if
> > the code starts to produce a flood of warnings at the dmesg.
> >
> > I would use pr_warn_ratelimit() on all such cases.
> >
>
> OK.
>
>
>
>
> > I don't like the idea of changing the "from" buffer endiannes, copy
> > and then restore it back to the original state. Is this really needed?
> >
> > I would, instead, define:
> >
> > struct pes_header {
> > ...
> > __be32 bitfield;
> > __be16 length;
> > ...
> > };
> >
> > Then wherever you would touch them:
> >
> > u32 bitfield;
> > u16 len;
> >
> > /* Write into BE fields */
> > pes_header.bitfield = cpu_to_be32(bitfield);
> > pes_header.length = cpu_to_be16(len);
> >
> > /* Read from BE fields */
> > bitfield = be32_to_cpu(pes_header.bitfield);
> > len = be16_to_cpu(pes_header.length);
> >
> >
> > As a side effect, when you use "__be16" and "__be32" types, gcc
> > and smatch/sparse will warn you if you mess with endiannes.
> >
> > Same applies to similar code elsewhere.
> >
>
> I don't like it either, it is error prone. I did not know about this
> other possibility. Does this work for _bitfields_ though?
See my comment below.
> I think the authors for libdvbv5 used unions precisely so bswap() could
> be called on a 'bitfield' member and from then on the bitfields could be
> accessed directly, e.g.:
>
> union {
> u16 bitfield; <-- call bswap() on this
> struct {
> --> then use these directly:
> u8 syntax:1;
> u8 zero:1;
> u8 one:2;
> u16 section_length:12;
> } __packed;
> } __packed
>
> At least that's what I understood.
You should double-check the structs from the specs. If I'm not mistaken,
bytes were swapped on some places. As I commented for patch 08/11,
the focus there were to make life simpler for userspace, and not to
store a precise copy of the byte order.
>
> I found this:
> https://lwn.net/Articles/741762/
>
> Maybe *_get_bits, *_replace_bits is the equivalent that I should use for bitfields?
I never used them, but, based on their definition:
static __always_inline base type##_get_bits(__##type v, base field) \
{ \
return (from(v) & field)/field_multiplier(field); \
}
Calling be16_get_bits should do the right cast to the type.
I don't know what the "from()" and "to()" macros would do.
I guess you will need to do some tests to see if this works as
expected.
>
> Because I'd rather not do this:
>
> > u32 bitfield;
> > /* Write into BE fields */
> > pes_header.bitfield = cpu_to_be32(bitfield);
>
> Since I'd have to write the (many!) bitwise operations myself and I'm
> sure I will mess this up at _some_ point.
If you mess up, gcc (and/or smatch) will complain. I mean,
if bitfield is declared as __be32, if you do:
u32 bitfield;
pes_header.bitfield = bitfield;
this will produce warnings.
Thanks,
Mauro
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
vidtv_demod-objs := vidtv_common.o
-vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o
+vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o
obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
@@ -42,3 +42,10 @@ u32 vidtv_memset(void *to,
memset(to, c, len);
return len;
}
+
+u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits)
+{
+ u64 mask = ((1 << nbits) - 1) << start_bit;
+
+ return value & mask;
+}
@@ -25,4 +25,6 @@ u32 vidtv_memset(void *to,
u32 offset,
u32 buf_sz);
+u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits);
+
#endif // VIDTV_COMMON_H
new file mode 100644
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the logic to translate the ES data for one access unit
+ * from an encoder into MPEG TS packets. It does so by first encapsulating it
+ * with a PES header and then splitting it into TS packets.
+ *
+ * Written by Daniel W. S. Almeida <dwlsalmeida@gmail.com>
+ */
+
+#include <linux/types.h>
+#include <linux/printk.h>
+#include "vidtv_pes.h"
+#include "vidtv_common.h"
+#include "vidtv_ts.h"
+
+#define PRIVATE_STREAM_1_ID 0xbd /* private_stream_1. See SMPTE 302M-2007 p.6 */
+#define PES_HEADER_MAX_STUFFING_BYTES 32
+#define PES_TS_HEADER_MAX_STUFFING_BYTES 182
+
+static u32 vidtv_pes_op_get_regular_len(bool send_pts, bool send_dts)
+{
+ u32 len = 0;
+
+ /* the flags must always be sent */
+ len += sizeof(struct vidtv_pes_optional);
+
+ /* From all optionals, we might send these for now */
+ if (send_pts && send_dts)
+ len += sizeof(struct vidtv_pes_optional_pts_dts);
+ else if (send_pts)
+ len += sizeof(struct vidtv_pes_optional_pts);
+
+ return len;
+}
+
+static u32 vidtv_pes_h_get_regular_len(bool send_pts, bool send_dts)
+{
+ /* PES header length notwithstanding stuffing bytes */
+ u32 len = 0;
+
+ len += sizeof(struct vidtv_mpeg_pes);
+ len += vidtv_pes_op_get_regular_len(send_pts, send_dts);
+
+ return len;
+}
+
+static u32 vidtv_pes_write_header_stuffing(struct vidtv_mpeg_pes *pes_h,
+ struct pes_header_write_args args)
+{
+ /*
+ * This is a fixed 8-bit value equal to '1111 1111' that can be inserted
+ * by the encoder, for example to meet the requirements of the channel.
+ * It is discarded by the decoder. No more than 32 stuffing bytes shall
+ * be present in one PES packet header.
+ */
+
+ WARN_ON(args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES);
+
+ if (args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES)
+ args.n_pes_h_s_bytes = PES_HEADER_MAX_STUFFING_BYTES;
+
+ /* gives the length of the remainder of the PES header in bytes */
+ pes_h->length += args.n_pes_h_s_bytes;
+
+ return vidtv_memset(args.dest_buf + args.dest_offset,
+ 0xff,
+ args.n_pes_h_s_bytes,
+ args.dest_offset,
+ args.dest_buf_sz);
+}
+
+static u32 vidtv_pes_write_pts_dts(struct pes_header_write_args args)
+{
+ u32 nbytes = 0; /* the number of bytes written by this function */
+
+ struct vidtv_pes_optional_pts pts = {0};
+ struct vidtv_pes_optional_pts_dts pts_dts = {0};
+ void *op = NULL;
+ size_t op_sz = 0;
+
+ if (!args.send_pts && args.send_dts)
+ return 0;
+
+ /* see ISO/IEC 13818-1 : 2000 p. 32 */
+
+ if (args.send_pts && args.send_dts) {
+ cpu_to_be64s(&args.pts);
+ cpu_to_be64s(&args.dts);
+ pts_dts.three = 0x3;
+
+ pts_dts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
+ pts_dts.marker1 = 0x1;
+ pts_dts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
+ pts_dts.marker2 = 0x1;
+ pts_dts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
+ pts_dts.marker3 = 0x1;
+
+ pts_dts.one = 0x1;
+
+ pts_dts.dts1 = vidtv_extract_bits(args.dts, 30, 3);
+ pts_dts.marker1 = 0x1;
+ pts_dts.dts2 = vidtv_extract_bits(args.dts, 15, 15);
+ pts_dts.marker2 = 0x1;
+ pts_dts.dts3 = vidtv_extract_bits(args.dts, 0, 15);
+ pts_dts.marker3 = 0x1;
+
+ be64_to_cpus(&args.pts);
+ be64_to_cpus(&args.dts);
+
+ op = &pts_dts;
+ op_sz = sizeof(pts_dts);
+
+ } else if (args.send_pts) {
+ cpu_to_be64s(&args.pts);
+ pts.two = 0x2;
+ pts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
+ pts.marker1 = 0x1;
+ pts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
+ pts.marker2 = 0x1;
+ pts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
+ pts.marker3 = 0x1;
+ be64_to_cpus(&args.pts);
+
+ op = &pts;
+ op_sz = sizeof(pts);
+ }
+
+ /* copy PTS/DTS optional */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ op,
+ op_sz,
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ return nbytes;
+}
+
+static u32 vidtv_pes_write_h(struct pes_header_write_args args)
+{
+ u32 nbytes = 0; /* the number of bytes written by this function */
+
+ struct vidtv_mpeg_pes pes_header = {0};
+ struct vidtv_pes_optional pes_optional = {0};
+ struct pes_header_write_args pts_dts_args = args;
+
+ pes_header.packet_start_code_prefix = PES_START_CODE_PREFIX;
+
+ pes_header.stream_id = (args.is_s302m_pkt) ?
+ PRIVATE_STREAM_1_ID :
+ args.stream_id;
+
+ pes_header.length = vidtv_pes_op_get_regular_len(args.send_pts,
+ args.send_dts);
+
+ pes_optional.two = 0x2;
+
+ pes_optional.PTS_DTS = (args.send_pts && args.send_dts) ?
+ 0x3 :
+ (args.send_pts) ?
+ 0x2 :
+ 0x0;
+
+ /* copy header */
+ cpu_to_be32s(&pes_header.bitfield);
+ cpu_to_be16s(&pes_header.length);
+
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &pes_header,
+ sizeof(pes_header),
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ be32_to_cpus(&pes_header.bitfield);
+ be16_to_cpus(&pes_header.length);
+
+ /* copy optional header */
+ cpu_to_be16s(&pes_optional.bitfield);
+
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &pes_optional,
+ sizeof(pes_optional),
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ be16_to_cpus(&pes_optional.bitfield);
+
+ pts_dts_args.dest_offset = args.dest_offset + nbytes;
+ nbytes += vidtv_pes_write_pts_dts(pts_dts_args);
+
+ /* write any PES header stuffing */
+ nbytes += vidtv_pes_write_header_stuffing(&pes_header, args);
+
+ return nbytes;
+}
+
+static u32 vidtv_pes_write_stuffing(struct vidtv_mpeg_ts *ts_h,
+ void *dest_buf,
+ u32 dest_offset,
+ u32 n_stuffing_bytes,
+ u32 buf_sz)
+{
+ /*
+ * For Transport Stream packets carrying PES packets, stuffing is
+ * needed when there is insufficient PES packet data to completely
+ * fill the Transport Stream packet payload bytes. Stuffing is
+ * accomplished by defining an adaptation field longer than the sum of
+ * the lengths of the data elements in it, so that the payload bytes
+ * remaining after the adaptation field exactly accommodates the
+ * available PES packet data. The extra space in the adaptation field
+ * is filled with stuffing bytes.
+ *
+ */
+
+ /* the number of bytes written by this function */
+ u32 nbytes = 0;
+ struct vidtv_mpeg_ts_adaption ts_adap = {0};
+
+ if (!n_stuffing_bytes)
+ return nbytes;
+
+ ts_h->adaptation_field = 1;
+
+ WARN_ON(n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES);
+
+ if (n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES)
+ n_stuffing_bytes = PES_TS_HEADER_MAX_STUFFING_BYTES;
+
+ /* the AF will only be its 'length' field with a value of zero */
+ if (n_stuffing_bytes == 1) {
+ nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
+ 0,
+ n_stuffing_bytes,
+ dest_offset + nbytes,
+ buf_sz);
+
+ return nbytes;
+ }
+
+ n_stuffing_bytes -= sizeof(ts_adap);
+
+ /* length _immediately_ following 'adaptation_field_length' */
+ ts_adap.length = sizeof(ts_adap) -
+ sizeof(ts_adap.length) +
+ n_stuffing_bytes;
+
+ /* write the adap after the TS header */
+ nbytes += vidtv_memcpy(dest_buf + dest_offset + nbytes,
+ &ts_adap,
+ sizeof(ts_adap),
+ dest_offset + nbytes,
+ buf_sz);
+
+ /* write the stuffing bytes */
+ nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
+ 0xff,
+ n_stuffing_bytes,
+ dest_offset + nbytes,
+ buf_sz);
+
+ return nbytes;
+}
+
+static u32 vidtv_pes_write_ts_h(struct pes_ts_header_write_args args)
+{
+ /* number of bytes written by this function */
+ u32 nbytes = 0;
+ struct vidtv_mpeg_ts ts_header = {0};
+
+ ts_header.sync_byte = TS_SYNC_BYTE;
+ ts_header.tei = 0;
+ ts_header.pid = args.pid;
+ ts_header.priority = 0;
+ ts_header.scrambling = 0; /* not scrambled */
+ ts_header.adaptation_field = 0;
+ ts_header.payload = 1;
+
+ ts_header.payload_start = (!args.wrote_pes_header) ? 1 : 0;
+ ts_header.continuity_counter = *args.continuity_counter;
+
+ vidtv_ts_inc_cc(args.continuity_counter);
+
+ cpu_to_be16s(&ts_header.bitfield);
+
+ /* write the TS header */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &ts_header,
+ sizeof(ts_header),
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ be16_to_cpus(&ts_header.bitfield);
+ /* write stuffing, if any */
+ nbytes += vidtv_pes_write_stuffing(&ts_header,
+ args.dest_buf,
+ args.dest_offset + nbytes,
+ args.n_stuffing_bytes,
+ args.dest_buf_sz);
+
+ return nbytes;
+}
+
+u32 vidtv_pes_write_into(struct pes_write_args args)
+{
+ u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN);
+ bool aligned = (nbytes_past_boundary == 0);
+
+ struct pes_ts_header_write_args ts_header_args = {0};
+ struct pes_header_write_args pes_header_args = {0};
+
+ /* number of bytes written by this function */
+ u32 nbytes = 0;
+ u32 remaining_len = args.access_unit_len;
+
+ bool wrote_pes_header = false;
+ bool stuff = false;
+
+ u32 available_space = 0;
+ u32 payload_write_len = 0;
+ u32 num_stuffing_bytes = 0;
+
+ /* Just a sanity check, should not really happen because we stuff the
+ * TS packet when we finish writing the PES data, but if this happens
+ * then we have messed up the logic somewhere.
+ *
+ * Also note that, unlike packets for PSI data, we need to carry PES
+ * packets aligned with the payload of transport packets, that is the
+ * first byte of each PES header must be the first byte in the payload
+ * of a transport packet. As a consequence, the last byte of a PES
+ * packet must be the last byte of the payload of a transport packet.
+ */
+ WARN_ON(!aligned);
+
+ if (args.send_dts && !args.send_pts) {
+ pr_warn("%s: forbidden value '01' for PTS_DTS flags", __func__);
+ args.send_pts = true;
+ args.pts = args.dts;
+ }
+
+ /* see SMPTE 302M clause 6.4 */
+ if (args.is_s302m_pkt) {
+ args.send_dts = false;
+ args.send_pts = true;
+ }
+
+ while (remaining_len) {
+ /*
+ * The amount of space initially available in the TS packet.
+ * if this is the beginning of the PES packet, we need to
+ * take into account the space needed for the TS header _and_
+ * for the PES header
+ */
+ available_space = (!wrote_pes_header) ?
+ TS_PAYLOAD_LEN -
+ vidtv_pes_h_get_regular_len(args.send_pts,
+ args.send_dts) :
+ TS_PAYLOAD_LEN;
+
+ /* if the encoder has inserted stuffing bytes in the PES
+ * header, account for them.
+ */
+ available_space -= args.n_pes_h_s_bytes;
+
+ /* whether we need to stuff the TS packet to align the buffer */
+ stuff = remaining_len < available_space;
+
+ /*
+ * how much of the _actual_ payload we should write in this
+ * packet.
+ */
+ payload_write_len = (stuff) ?
+ remaining_len :
+ available_space;
+
+ num_stuffing_bytes = available_space - payload_write_len;
+
+ /* write ts header */
+ ts_header_args.dest_buf = args.dest_buf;
+ ts_header_args.dest_offset = args.dest_offset + nbytes;
+ ts_header_args.dest_buf_sz = args.dest_buf_sz;
+ ts_header_args.pid = args.pid;
+ ts_header_args.continuity_counter = args.continuity_counter;
+ ts_header_args.wrote_pes_header = wrote_pes_header;
+ ts_header_args.n_stuffing_bytes = num_stuffing_bytes;
+
+ nbytes += vidtv_pes_write_ts_h(ts_header_args);
+
+ if (!wrote_pes_header) {
+ /* write the PES header only once */
+ pes_header_args.dest_buf = args.dest_buf;
+
+ pes_header_args.dest_offset = args.dest_offset +
+ nbytes;
+
+ pes_header_args.dest_buf_sz = args.dest_buf_sz;
+ pes_header_args.is_s302m_pkt = args.is_s302m_pkt;
+ pes_header_args.send_pts = args.send_pts;
+ pes_header_args.pts = args.pts;
+ pes_header_args.send_dts = args.send_dts;
+ pes_header_args.dts = args.dts;
+ pes_header_args.stream_id = args.stream_id;
+ pes_header_args.n_pes_h_s_bytes = args.n_pes_h_s_bytes;
+
+ nbytes += vidtv_pes_write_h(pes_header_args);
+ wrote_pes_header = true;
+ }
+
+ /* write as much of the payload as we possibly can */
+ nbytes += vidtv_memcpy(args.dest_buf +
+ args.dest_offset +
+ nbytes,
+ args.from,
+ payload_write_len,
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ args.from += payload_write_len;
+ args.dest_offset += nbytes;
+
+ /* sanity check for underflow */
+ WARN_ON(remaining_len - payload_write_len > remaining_len);
+ remaining_len -= payload_write_len;
+ }
+
+ return nbytes;
+}
new file mode 100644
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the logic to translate the ES data for one access unit
+ * from an encoder into MPEG TS packets. It does so by first encapsulating it
+ * with a PES header and then splitting it into TS packets.
+ *
+ * Written by Daniel W. S. Almeida <dwlsalmeida@gmail.com>
+ */
+
+#ifndef VIDTV_PES_H
+#define VIDTV_PES_H
+
+#include <asm/byteorder.h>
+#include <linux/types.h>
+#include "vidtv_common.h"
+
+#define PES_MAX_LEN 65536 /* Set 'length' to 0 if greater */
+#define PES_START_CODE_PREFIX 0x001 /* 00 00 01 */
+
+struct vidtv_pes_optional_pts {
+ struct {
+ #if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 marker3:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker1:1; /* always 0x1 */
+ u8 pts1:3;
+ u8 two:4; /* always 0010b */
+ #elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 two:4; /* always 0010b */
+ u8 pts1:3;
+ u8 marker1:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker3:1; /* always 0x1 */
+ #else
+ #error "Please fix <asm/byteorder.h>"
+ #endif
+ } __packed;
+} __packed;
+
+struct vidtv_pes_optional_pts_dts {
+ struct {
+ #if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 marker6:1; /* always 0x1 */
+ u16 dts3:15;
+ u8 marker5:1; /* always 0x1 */
+ u16 dts2:15;
+ u8 marker4:1; /* always 0x1 */
+ u8 dts1:3;
+ u8 one:4; /* always 0001b */
+ u8 marker3:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker1:1; /* always 0x1 */
+ u8 pts1:3;
+ u8 three:4; /* always 0011b */
+ #elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 three:4; /* always 0011b */
+ u8 pts1:3;
+ u8 marker1:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker3:1; /* always 0x1 */
+ u8 one:4; /* always 0001b */
+ u8 dts1:3;
+ u8 marker4:1; /* always 0x1 */
+ u16 dts2:15;
+ u8 marker5:1; /* always 0x1 */
+ u16 dts3:15;
+ u8 marker6:1; /* always 0x1 */
+ #else
+ #error "Please fix <asm/byteorder.h>"
+ #endif
+ } __packed;
+} __packed;
+
+struct vidtv_pes_optional {
+ union {
+ u16 bitfield;
+ struct {
+ u16 two:2; /* always 0x2*/
+ u16 PES_scrambling_control:2;
+ u16 PES_priority:1;
+ u16 data_alignment_indicator:1; /* ununsed for us */
+ u16 copyright:1;
+ u16 original_or_copy:1;
+ u16 PTS_DTS:2;
+ /* These flags show which components are actually
+ * present in the "optinal fields" in the optinal PES
+ * header and which are not. Vidtv currently does
+ * not need any of these.
+ */
+ u16 ESCR:1;
+ u16 ES_rate:1;
+ u16 DSM_trick_mode:1;
+ u16 additional_copy_info:1;
+ u16 PES_CRC:1;
+ u16 PES_extension:1;
+ } __packed;
+ } __packed;
+ u8 length;
+} __packed;
+
+struct vidtv_mpeg_pes {
+ union {
+ u32 bitfield;
+ struct {
+ /* These two together make the 32-bit start-code */
+ u32 packet_start_code_prefix:24;
+ u32 stream_id:8;
+ } __packed;
+ } __packed;
+ /* after this field until the end of the PES data payload */
+ u16 length;
+ struct vidtv_pes_optional optional[];
+} __packed;
+
+struct pes_header_write_args {
+ void *dest_buf;
+ u32 dest_offset;
+ u32 dest_buf_sz;
+ bool is_s302m_pkt;
+
+ bool send_pts;
+ u64 pts;
+
+ bool send_dts;
+ u64 dts;
+
+ u16 stream_id;
+ /* might be used by an encoder if needed, gets discarded by decoder */
+ u32 n_pes_h_s_bytes;
+};
+
+struct pes_ts_header_write_args {
+ void *dest_buf;
+ u32 dest_offset;
+ u32 dest_buf_sz;
+ u16 pid;
+ u8 *continuity_counter;
+ bool wrote_pes_header;
+ u32 n_stuffing_bytes;
+};
+
+struct pes_write_args {
+ void *dest_buf; /* pointer to a program mux buffer */
+ void *from; /* pointer to the encoder buffer */
+
+ /* the size of one access unit (with any headers it might need) */
+ u32 access_unit_len;
+
+ u32 dest_offset; /* where to start writing in the program mux buffer */
+ u32 dest_buf_sz; /* how big is the program mux buffer */
+ u16 pid; /* TS packet ID */
+
+ /* use SMPTE 302M to packetize the data */
+ bool is_s302m_pkt;
+
+ u8 *continuity_counter; /* incremented for every TS packet */
+
+ /* Examples: Audio streams (0xc0-0xdf), Video streams (0xe0-0xef) */
+ u16 stream_id;
+
+ bool send_pts;
+ u64 pts;
+
+ bool send_dts;
+ u64 dts;
+
+ /* might be used by an encoder if needed, gets discarded by decoder */
+ u32 n_pes_h_s_bytes;
+};
+
+u32 vidtv_pes_write_into(struct pes_write_args args);
+
+#endif // VIDTV_PES_H