mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
425 lines
12 KiB
425 lines
12 KiB
// 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. |
|
* |
|
* Copyright (C) 2020 Daniel W. S. Almeida |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ |
|
|
|
#include <linux/types.h> |
|
#include <linux/printk.h> |
|
#include <linux/ratelimit.h> |
|
|
|
#include "vidtv_pes.h" |
|
#include "vidtv_common.h" |
|
#include "vidtv_encoder.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_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; |
|
} |
|
|
|
#define SIZE_PCR (6 + sizeof(struct vidtv_mpeg_ts_adaption)) |
|
|
|
static u32 vidtv_pes_h_get_len(bool send_pts, bool send_dts) |
|
{ |
|
u32 len = 0; |
|
|
|
/* PES header length notwithstanding stuffing bytes */ |
|
|
|
len += sizeof(struct vidtv_mpeg_pes); |
|
len += vidtv_pes_op_get_len(send_pts, send_dts); |
|
|
|
return len; |
|
} |
|
|
|
static u32 vidtv_pes_write_header_stuffing(struct pes_header_write_args *args) |
|
{ |
|
/* |
|
* This is a fixed 8-bit value equal to '0xFF' 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. |
|
*/ |
|
if (args->n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES) { |
|
pr_warn_ratelimited("More than %d stuffing bytes in PES packet header\n", |
|
PES_HEADER_MAX_STUFFING_BYTES); |
|
args->n_pes_h_s_bytes = PES_HEADER_MAX_STUFFING_BYTES; |
|
} |
|
|
|
return vidtv_memset(args->dest_buf, |
|
args->dest_offset, |
|
args->dest_buf_sz, |
|
TS_FILL_BYTE, |
|
args->n_pes_h_s_bytes); |
|
} |
|
|
|
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 = {}; |
|
struct vidtv_pes_optional_pts_dts pts_dts = {}; |
|
void *op = NULL; |
|
size_t op_sz = 0; |
|
u64 mask1; |
|
u64 mask2; |
|
u64 mask3; |
|
|
|
if (!args->send_pts && args->send_dts) |
|
return 0; |
|
|
|
mask1 = GENMASK_ULL(32, 30); |
|
mask2 = GENMASK_ULL(29, 15); |
|
mask3 = GENMASK_ULL(14, 0); |
|
|
|
/* see ISO/IEC 13818-1 : 2000 p. 32 */ |
|
if (args->send_pts && args->send_dts) { |
|
pts_dts.pts1 = (0x3 << 4) | ((args->pts & mask1) >> 29) | 0x1; |
|
pts_dts.pts2 = cpu_to_be16(((args->pts & mask2) >> 14) | 0x1); |
|
pts_dts.pts3 = cpu_to_be16(((args->pts & mask3) << 1) | 0x1); |
|
|
|
pts_dts.dts1 = (0x1 << 4) | ((args->dts & mask1) >> 29) | 0x1; |
|
pts_dts.dts2 = cpu_to_be16(((args->dts & mask2) >> 14) | 0x1); |
|
pts_dts.dts3 = cpu_to_be16(((args->dts & mask3) << 1) | 0x1); |
|
|
|
op = &pts_dts; |
|
op_sz = sizeof(pts_dts); |
|
|
|
} else if (args->send_pts) { |
|
pts.pts1 = (0x1 << 5) | ((args->pts & mask1) >> 29) | 0x1; |
|
pts.pts2 = cpu_to_be16(((args->pts & mask2) >> 14) | 0x1); |
|
pts.pts3 = cpu_to_be16(((args->pts & mask3) << 1) | 0x1); |
|
|
|
op = &pts; |
|
op_sz = sizeof(pts); |
|
} |
|
|
|
/* copy PTS/DTS optional */ |
|
nbytes += vidtv_memcpy(args->dest_buf, |
|
args->dest_offset + nbytes, |
|
args->dest_buf_sz, |
|
op, |
|
op_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 = {}; |
|
struct vidtv_pes_optional pes_optional = {}; |
|
struct pes_header_write_args pts_dts_args; |
|
u32 stream_id = (args->encoder_id == S302M) ? PRIVATE_STREAM_1_ID : args->stream_id; |
|
u16 pes_opt_bitfield = 0x01 << 15; |
|
|
|
pes_header.bitfield = cpu_to_be32((PES_START_CODE_PREFIX << 8) | stream_id); |
|
|
|
pes_header.length = cpu_to_be16(vidtv_pes_op_get_len(args->send_pts, |
|
args->send_dts) + |
|
args->access_unit_len); |
|
|
|
if (args->send_pts && args->send_dts) |
|
pes_opt_bitfield |= (0x3 << 6); |
|
else if (args->send_pts) |
|
pes_opt_bitfield |= (0x1 << 7); |
|
|
|
pes_optional.bitfield = cpu_to_be16(pes_opt_bitfield); |
|
pes_optional.length = vidtv_pes_op_get_len(args->send_pts, args->send_dts) + |
|
args->n_pes_h_s_bytes - |
|
sizeof(struct vidtv_pes_optional); |
|
|
|
/* copy header */ |
|
nbytes += vidtv_memcpy(args->dest_buf, |
|
args->dest_offset + nbytes, |
|
args->dest_buf_sz, |
|
&pes_header, |
|
sizeof(pes_header)); |
|
|
|
/* copy optional header bits */ |
|
nbytes += vidtv_memcpy(args->dest_buf, |
|
args->dest_offset + nbytes, |
|
args->dest_buf_sz, |
|
&pes_optional, |
|
sizeof(pes_optional)); |
|
|
|
/* copy the timing information */ |
|
pts_dts_args = *args; |
|
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(args); |
|
|
|
return nbytes; |
|
} |
|
|
|
static u32 vidtv_pes_write_pcr_bits(u8 *to, u32 to_offset, u64 pcr) |
|
{ |
|
/* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */ |
|
u64 div; |
|
u64 rem; |
|
u8 *buf = to + to_offset; |
|
u64 pcr_low; |
|
u64 pcr_high; |
|
|
|
div = div64_u64_rem(pcr, 300, &rem); |
|
|
|
pcr_low = rem; /* pcr_low = pcr % 300 */ |
|
pcr_high = div; /* pcr_high = pcr / 300 */ |
|
|
|
*buf++ = pcr_high >> 25; |
|
*buf++ = pcr_high >> 17; |
|
*buf++ = pcr_high >> 9; |
|
*buf++ = pcr_high >> 1; |
|
*buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e; |
|
*buf++ = pcr_low; |
|
|
|
return 6; |
|
} |
|
|
|
static u32 vidtv_pes_write_stuffing(struct pes_ts_header_write_args *args, |
|
u32 dest_offset, bool need_pcr, |
|
u64 *last_pcr) |
|
{ |
|
struct vidtv_mpeg_ts_adaption ts_adap = {}; |
|
int stuff_nbytes; |
|
u32 nbytes = 0; |
|
|
|
if (!args->n_stuffing_bytes) |
|
return 0; |
|
|
|
ts_adap.random_access = 1; |
|
|
|
/* length _immediately_ following 'adaptation_field_length' */ |
|
if (need_pcr) { |
|
ts_adap.PCR = 1; |
|
ts_adap.length = SIZE_PCR; |
|
} else { |
|
ts_adap.length = sizeof(ts_adap); |
|
} |
|
stuff_nbytes = args->n_stuffing_bytes - ts_adap.length; |
|
|
|
ts_adap.length -= sizeof(ts_adap.length); |
|
|
|
if (unlikely(stuff_nbytes < 0)) |
|
stuff_nbytes = 0; |
|
|
|
ts_adap.length += stuff_nbytes; |
|
|
|
/* write the adap after the TS header */ |
|
nbytes += vidtv_memcpy(args->dest_buf, |
|
dest_offset + nbytes, |
|
args->dest_buf_sz, |
|
&ts_adap, |
|
sizeof(ts_adap)); |
|
|
|
/* write the optional PCR */ |
|
if (need_pcr) { |
|
nbytes += vidtv_pes_write_pcr_bits(args->dest_buf, |
|
dest_offset + nbytes, |
|
args->pcr); |
|
|
|
*last_pcr = args->pcr; |
|
} |
|
|
|
/* write the stuffing bytes, if are there anything left */ |
|
if (stuff_nbytes) |
|
nbytes += vidtv_memset(args->dest_buf, |
|
dest_offset + nbytes, |
|
args->dest_buf_sz, |
|
TS_FILL_BYTE, |
|
stuff_nbytes); |
|
|
|
/* |
|
* The n_stuffing_bytes contain a pre-calculated value of |
|
* the amount of data that this function would read, made from |
|
* vidtv_pes_h_get_len(). If something went wrong, print a warning |
|
*/ |
|
if (nbytes != args->n_stuffing_bytes) |
|
pr_warn_ratelimited("write size was %d, expected %d\n", |
|
nbytes, args->n_stuffing_bytes); |
|
|
|
return nbytes; |
|
} |
|
|
|
static u32 vidtv_pes_write_ts_h(struct pes_ts_header_write_args args, |
|
bool need_pcr, u64 *last_pcr) |
|
{ |
|
/* number of bytes written by this function */ |
|
u32 nbytes = 0; |
|
struct vidtv_mpeg_ts ts_header = {}; |
|
u16 payload_start = !args.wrote_pes_header; |
|
|
|
ts_header.sync_byte = TS_SYNC_BYTE; |
|
ts_header.bitfield = cpu_to_be16((payload_start << 14) | args.pid); |
|
ts_header.scrambling = 0; |
|
ts_header.adaptation_field = (args.n_stuffing_bytes) > 0; |
|
ts_header.payload = (args.n_stuffing_bytes) < PES_TS_HEADER_MAX_STUFFING_BYTES; |
|
|
|
ts_header.continuity_counter = *args.continuity_counter; |
|
|
|
vidtv_ts_inc_cc(args.continuity_counter); |
|
|
|
/* write the TS header */ |
|
nbytes += vidtv_memcpy(args.dest_buf, |
|
args.dest_offset + nbytes, |
|
args.dest_buf_sz, |
|
&ts_header, |
|
sizeof(ts_header)); |
|
|
|
/* write stuffing, if any */ |
|
nbytes += vidtv_pes_write_stuffing(&args, args.dest_offset + nbytes, |
|
need_pcr, last_pcr); |
|
|
|
return nbytes; |
|
} |
|
|
|
u32 vidtv_pes_write_into(struct pes_write_args *args) |
|
{ |
|
u32 unaligned_bytes = (args->dest_offset % TS_PACKET_LEN); |
|
struct pes_ts_header_write_args ts_header_args = { |
|
.dest_buf = args->dest_buf, |
|
.dest_buf_sz = args->dest_buf_sz, |
|
.pid = args->pid, |
|
.pcr = args->pcr, |
|
.continuity_counter = args->continuity_counter, |
|
}; |
|
struct pes_header_write_args pes_header_args = { |
|
.dest_buf = args->dest_buf, |
|
.dest_buf_sz = args->dest_buf_sz, |
|
.encoder_id = args->encoder_id, |
|
.send_pts = args->send_pts, |
|
.pts = args->pts, |
|
.send_dts = args->send_dts, |
|
.dts = args->dts, |
|
.stream_id = args->stream_id, |
|
.n_pes_h_s_bytes = args->n_pes_h_s_bytes, |
|
.access_unit_len = args->access_unit_len, |
|
}; |
|
u32 remaining_len = args->access_unit_len; |
|
bool wrote_pes_header = false; |
|
u64 last_pcr = args->pcr; |
|
bool need_pcr = true; |
|
u32 available_space; |
|
u32 payload_size; |
|
u32 stuff_bytes; |
|
u32 nbytes = 0; |
|
|
|
if (unaligned_bytes) { |
|
pr_warn_ratelimited("buffer is misaligned, while starting PES\n"); |
|
|
|
/* forcibly align and hope for the best */ |
|
nbytes += vidtv_memset(args->dest_buf, |
|
args->dest_offset + nbytes, |
|
args->dest_buf_sz, |
|
TS_FILL_BYTE, |
|
TS_PACKET_LEN - unaligned_bytes); |
|
} |
|
|
|
while (remaining_len) { |
|
available_space = TS_PAYLOAD_LEN; |
|
/* |
|
* The amount of space initially available in the TS packet. |
|
* if this is the beginning of the PES packet, take into account |
|
* the space needed for the TS header _and_ for the PES header |
|
*/ |
|
if (!wrote_pes_header) |
|
available_space -= vidtv_pes_h_get_len(args->send_pts, |
|
args->send_dts); |
|
|
|
/* |
|
* if the encoder has inserted stuffing bytes in the PES |
|
* header, account for them. |
|
*/ |
|
available_space -= args->n_pes_h_s_bytes; |
|
|
|
/* Take the extra adaptation into account if need to send PCR */ |
|
if (need_pcr) { |
|
available_space -= SIZE_PCR; |
|
stuff_bytes = SIZE_PCR; |
|
} else { |
|
stuff_bytes = 0; |
|
} |
|
|
|
/* |
|
* how much of the _actual_ payload should be written in this |
|
* packet. |
|
*/ |
|
if (remaining_len >= available_space) { |
|
payload_size = available_space; |
|
} else { |
|
/* Last frame should ensure 188-bytes PS alignment */ |
|
payload_size = remaining_len; |
|
stuff_bytes += available_space - payload_size; |
|
|
|
/* |
|
* Ensure that the stuff bytes will be within the |
|
* allowed range, decrementing the number of payload |
|
* bytes to write if needed. |
|
*/ |
|
if (stuff_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES) { |
|
u32 tmp = stuff_bytes - PES_TS_HEADER_MAX_STUFFING_BYTES; |
|
|
|
stuff_bytes = PES_TS_HEADER_MAX_STUFFING_BYTES; |
|
payload_size -= tmp; |
|
} |
|
} |
|
|
|
/* write ts header */ |
|
ts_header_args.dest_offset = args->dest_offset + nbytes; |
|
ts_header_args.wrote_pes_header = wrote_pes_header; |
|
ts_header_args.n_stuffing_bytes = stuff_bytes; |
|
|
|
nbytes += vidtv_pes_write_ts_h(ts_header_args, need_pcr, |
|
&last_pcr); |
|
|
|
need_pcr = false; |
|
|
|
if (!wrote_pes_header) { |
|
/* write the PES header only once */ |
|
pes_header_args.dest_offset = args->dest_offset + |
|
nbytes; |
|
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->dest_buf_sz, |
|
args->from, |
|
payload_size); |
|
|
|
args->from += payload_size; |
|
|
|
remaining_len -= payload_size; |
|
} |
|
|
|
return nbytes; |
|
}
|
|
|