// 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; 
 | 
} 
 |