media: vidtv: add a bridge driver

Digital TV devices consist of several independent hardware components
which are controlled by different drivers.
Each media device is controlled by a group of cooperating drivers with the
bridge driver as the main driver.

This patch adds a bridge driver for the Virtual Digital TV driver [vidtv].

The bridge driver binds to the other drivers, that is, vidtv_tuner and
vidtv_demod and implements the digital demux logic, providing userspace
with a MPEG Transport Stream.

The MPEG related code is split in the following way:

- vidtv_ts: code to work with MPEG TS packets, such as TS headers,
adaptation fields, PCR packets and NULL packets.

- vidtv_psi: this is the PSI generator.
PSI packets contain general information about a MPEG Transport Stream.
A PSI generator is needed so userspace apps can retrieve information
about the Transport Stream and eventually tune into a (dummy) channel.

Because the generator is implemented in a separate file, it can be
reused elsewhere in the media subsystem.

Currently vidtv supports working with 3 PSI tables:
PAT, PMT and SDT.

- vidtv_pes: implements the PES logic to convert encoder data into
MPEG TS packets. These can then be fed into a TS multiplexer and
eventually into userspace.

- vidtv_s302m: implements a S302M encoder to make it possible to
insert PCM audio data in the generated MPEG Transport Stream.

This shall enable passing an audio signal into userspace so it can be
decoded and played by media software.

- vidtv_channels: Implements a 'channel' abstraction

When vidtv boots, it will create some hardcoded channels:

Their services will be concatenated to populate the SDT.
Their programs will be concatenated to populate the PAT
For each program in the PAT, a PMT section will be created
The PMT section for a channel will be assigned its streams.
Every stream will have its corresponding encoder polled to produce TS
packets
These packets may be interleaved by the mux and then delivered to the
bridge

- vidtv_mux - Implements a MPEG TS mux, loosely based on the ffmpeg
implementation

The multiplexer is responsible for polling encoders,
interleaving packets, padding the resulting stream with NULL packets if
necessary and then delivering the resulting TS packets to the bridge
driver so it can feed the demux.

Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
This commit is contained in:
Daniel W. S. Almeida
2020-08-21 14:58:47 +02:00
committed by Mauro Carvalho Chehab
parent f5ffc3b6ed
commit f90cf6079b
18 changed files with 5290 additions and 1 deletions

View File

@@ -2,5 +2,8 @@
dvb-vidtv-tuner-objs := vidtv_tuner.o
dvb-vidtv-demod-objs := vidtv_demod.o
dvb-vidtv-bridge-objs := vidtv_bridge.o vidtv_common.o vidtv_ts.o vidtv_psi.o \
vidtv_pes.o vidtv_s302m.o vidtv_channel.o vidtv_mux.o
obj-$(CONFIG_DVB_VIDTV) += dvb-vidtv-tuner.o dvb-vidtv-demod.o
obj-$(CONFIG_DVB_VIDTV) += dvb-vidtv-tuner.o dvb-vidtv-demod.o \
dvb-vidtv-bridge.o

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* The Virtual DTV test driver 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.
*
* When this module is loaded, it will attempt to modprobe 'dvb_vidtv_tuner' and 'dvb_vidtv_demod'.
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#ifndef VIDTV_BRIDGE_H
#define VIDTV_BRIDGE_H
#define NUM_FE 1
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <media/dmxdev.h>
#include <media/dvb_demux.h>
#include <media/dvb_frontend.h>
#include "vidtv_mux.h"
/**
* struct vidtv_dvb - Vidtv bridge state
* @pdev: The platform device. Obtained when the bridge is probed.
* @fe: The frontends. Obtained when probing the demodulator modules.
* @adapter: Represents a DTV adapter. See 'dvb_register_adapter'.
* @demux: The demux used by the dvb_dmx_swfilter_packets() call.
* @dmx_dev: Represents a demux device.
* @dmx_frontend: The frontends associated with the demux.
* @i2c_adapter: The i2c_adapter associated with the bridge driver.
* @i2c_client_demod: The i2c_clients associated with the demodulator modules.
* @i2c_client_tuner: The i2c_clients associated with the tuner modules.
* @nfeeds: The number of feeds active.
* @feed_lock: Protects access to the start/stop stream logic/data.
* @streaming: Whether we are streaming now.
* @mux: The abstraction responsible for delivering MPEG TS packets to the bridge.
*/
struct vidtv_dvb {
struct platform_device *pdev;
struct dvb_frontend *fe[NUM_FE];
struct dvb_adapter adapter;
struct dvb_demux demux;
struct dmxdev dmx_dev;
struct dmx_frontend dmx_fe[NUM_FE];
struct i2c_adapter i2c_adapter;
struct i2c_client *i2c_client_demod[NUM_FE];
struct i2c_client *i2c_client_tuner[NUM_FE];
u32 nfeeds;
struct mutex feed_lock; /* Protects access to the start/stop stream logic/data. */
bool streaming;
struct vidtv_mux *mux;
};
#endif // VIDTV_BRIDG_H

View File

@@ -0,0 +1,306 @@
// 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 code for a 'channel' abstraction.
*
* When vidtv boots, it will create some hardcoded channels.
* Their services will be concatenated to populate the SDT.
* Their programs will be concatenated to populate the PAT
* For each program in the PAT, a PMT section will be created
* The PMT section for a channel will be assigned its streams.
* Every stream will have its corresponding encoder polled to produce TS packets
* These packets may be interleaved by the mux and then delivered to the bridge
*
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/printk.h>
#include <linux/ratelimit.h>
#include "vidtv_channel.h"
#include "vidtv_psi.h"
#include "vidtv_encoder.h"
#include "vidtv_mux.h"
#include "vidtv_common.h"
#include "vidtv_s302m.h"
static void vidtv_channel_encoder_destroy(struct vidtv_encoder *e)
{
struct vidtv_encoder *curr = e;
struct vidtv_encoder *tmp = NULL;
while (curr) {
/* forward the call to the derived type */
tmp = curr;
curr = curr->next;
tmp->destroy(tmp);
}
}
struct vidtv_channel
*vidtv_channel_s302m_init(struct vidtv_channel *head, u16 transport_stream_id)
{
/*
* init an audio only channel with a s302m encoder
*/
const u16 s302m_service_id = 0x880;
const u16 s302m_program_num = 0x880;
const u16 s302m_program_pid = 0x101; /* packet id for PMT*/
const u16 s302m_es_pid = 0x111; /* packet id for the ES */
const __be32 s302m_fid = cpu_to_be32(VIDTV_S302M_FORMAT_IDENTIFIER);
char *name = "S302m: Sine Wave PCM Audio";
struct vidtv_channel *s302m = kzalloc(sizeof(*s302m), GFP_KERNEL);
struct vidtv_s302m_encoder_init_args encoder_args = {};
s302m->name = kstrdup(name, GFP_KERNEL);
s302m->service = vidtv_psi_sdt_service_init(NULL, s302m_service_id);
s302m->service->descriptor = (struct vidtv_psi_desc *)
vidtv_psi_service_desc_init(NULL,
DIGITAL_TELEVISION_SERVICE,
name,
NULL);
s302m->transport_stream_id = transport_stream_id;
s302m->program = vidtv_psi_pat_program_init(NULL,
s302m_service_id,
s302m_program_pid);
s302m->program_num = s302m_program_num;
s302m->streams = vidtv_psi_pmt_stream_init(NULL,
STREAM_PRIVATE_DATA,
s302m_es_pid);
s302m->streams->descriptor = (struct vidtv_psi_desc *)
vidtv_psi_registration_desc_init(NULL,
s302m_fid,
NULL,
0);
encoder_args.es_pid = s302m_es_pid;
s302m->encoders = vidtv_s302m_encoder_init(encoder_args);
if (head) {
while (head->next)
head = head->next;
head->next = s302m;
}
return s302m;
}
static struct vidtv_psi_table_sdt_service
*vidtv_channel_sdt_serv_cat_into_new(const struct vidtv_channel *channels)
{
/* Concatenate the services */
const struct vidtv_channel *cur_chnl = channels;
struct vidtv_psi_table_sdt_service *curr = NULL;
struct vidtv_psi_table_sdt_service *head = NULL;
struct vidtv_psi_table_sdt_service *tail = NULL;
struct vidtv_psi_desc *desc = NULL;
u16 service_id;
if (!cur_chnl)
return NULL;
while (cur_chnl) {
curr = cur_chnl->service;
if (!curr)
pr_warn_ratelimited("No services found for channel %s\n", cur_chnl->name);
while (curr) {
service_id = be16_to_cpu(curr->service_id);
tail = vidtv_psi_sdt_service_init(tail, service_id);
desc = vidtv_psi_desc_clone(curr->descriptor);
vidtv_psi_desc_assign(&tail->descriptor, desc);
if (!head)
head = tail;
curr = curr->next;
}
cur_chnl = cur_chnl->next;
}
return head;
}
static struct vidtv_psi_table_pat_program*
vidtv_channel_pat_prog_cat_into_new(const struct vidtv_channel *channels)
{
/* Concatenate the programs */
const struct vidtv_channel *cur_chnl = channels;
struct vidtv_psi_table_pat_program *curr = NULL;
struct vidtv_psi_table_pat_program *head = NULL;
struct vidtv_psi_table_pat_program *tail = NULL;
u16 serv_id;
u16 pid;
if (!cur_chnl)
return NULL;
while (cur_chnl) {
curr = cur_chnl->program;
if (!curr)
pr_warn_ratelimited("No programs found for channel %s\n", cur_chnl->name);
while (curr) {
serv_id = be16_to_cpu(curr->service_id);
pid = vidtv_psi_get_pat_program_pid(curr);
tail = vidtv_psi_pat_program_init(tail,
serv_id,
pid);
if (!head)
head = tail;
curr = curr->next;
}
cur_chnl = cur_chnl->next;
}
return head;
}
static void
vidtv_channel_pmt_match_sections(struct vidtv_channel *channels,
struct vidtv_psi_table_pmt **sections,
u32 nsections)
{
/*
* Match channels to their respective PMT sections, then assign the
* streams
*/
struct vidtv_psi_table_pmt *curr_section = NULL;
struct vidtv_channel *cur_chnl = channels;
struct vidtv_psi_table_pmt_stream *s = NULL;
struct vidtv_psi_table_pmt_stream *head = NULL;
struct vidtv_psi_table_pmt_stream *tail = NULL;
struct vidtv_psi_desc *desc = NULL;
u32 j;
u16 curr_id;
u16 e_pid; /* elementary stream pid */
while (cur_chnl) {
for (j = 0; j < nsections; ++j) {
curr_section = sections[j];
if (!curr_section)
continue;
curr_id = be16_to_cpu(curr_section->header.id);
/* we got a match */
if (curr_id == cur_chnl->program_num) {
s = cur_chnl->streams;
/* clone the streams for the PMT */
while (s) {
e_pid = vidtv_psi_pmt_stream_get_elem_pid(s);
tail = vidtv_psi_pmt_stream_init(tail,
s->type,
e_pid);
if (!head)
head = tail;
desc = vidtv_psi_desc_clone(s->descriptor);
vidtv_psi_desc_assign(&tail->descriptor, desc);
s = s->next;
}
vidtv_psi_pmt_stream_assign(curr_section, head);
break;
}
}
cur_chnl = cur_chnl->next;
}
}
void vidtv_channel_si_init(struct vidtv_mux *m)
{
struct vidtv_psi_table_pat_program *programs = NULL;
struct vidtv_psi_table_sdt_service *services = NULL;
m->si.pat = vidtv_psi_pat_table_init(m->transport_stream_id);
m->si.sdt = vidtv_psi_sdt_table_init(m->transport_stream_id);
programs = vidtv_channel_pat_prog_cat_into_new(m->channels);
services = vidtv_channel_sdt_serv_cat_into_new(m->channels);
/* assemble all programs and assign to PAT */
vidtv_psi_pat_program_assign(m->si.pat, programs);
/* assemble all services and assign to SDT */
vidtv_psi_sdt_service_assign(m->si.sdt, services);
m->si.pmt_secs = vidtv_psi_pmt_create_sec_for_each_pat_entry(m->si.pat, m->pcr_pid);
vidtv_channel_pmt_match_sections(m->channels,
m->si.pmt_secs,
m->si.pat->programs);
}
void vidtv_channel_si_destroy(struct vidtv_mux *m)
{
u32 i;
u16 num_programs = m->si.pat->programs;
vidtv_psi_pat_table_destroy(m->si.pat);
for (i = 0; i < num_programs; ++i)
vidtv_psi_pmt_table_destroy(m->si.pmt_secs[i]);
kfree(m->si.pmt_secs);
vidtv_psi_sdt_table_destroy(m->si.sdt);
}
void vidtv_channels_init(struct vidtv_mux *m)
{
/* this is the place to add new 'channels' for vidtv */
m->channels = vidtv_channel_s302m_init(NULL, m->transport_stream_id);
}
void vidtv_channels_destroy(struct vidtv_mux *m)
{
struct vidtv_channel *curr = m->channels;
struct vidtv_channel *tmp = NULL;
while (curr) {
kfree(curr->name);
vidtv_psi_sdt_service_destroy(curr->service);
vidtv_psi_pat_program_destroy(curr->program);
vidtv_psi_pmt_stream_destroy(curr->streams);
vidtv_channel_encoder_destroy(curr->encoders);
tmp = curr;
curr = curr->next;
kfree(tmp);
}
}

View File

@@ -0,0 +1,76 @@
/* 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 code for a 'channel' abstraction.
*
* When vidtv boots, it will create some hardcoded channels.
* Their services will be concatenated to populate the SDT.
* Their programs will be concatenated to populate the PAT
* For each program in the PAT, a PMT section will be created
* The PMT section for a channel will be assigned its streams.
* Every stream will have its corresponding encoder polled to produce TS packets
* These packets may be interleaved by the mux and then delivered to the bridge
*
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#ifndef VIDTV_CHANNEL_H
#define VIDTV_CHANNEL_H
#include <linux/types.h>
#include "vidtv_psi.h"
#include "vidtv_encoder.h"
#include "vidtv_mux.h"
/**
* struct vidtv_channel - A 'channel' abstraction
*
* When vidtv boots, it will create some hardcoded channels.
* Their services will be concatenated to populate the SDT.
* Their programs will be concatenated to populate the PAT
* For each program in the PAT, a PMT section will be created
* The PMT section for a channel will be assigned its streams.
* Every stream will have its corresponding encoder polled to produce TS packets
* These packets may be interleaved by the mux and then delivered to the bridge
*
* @transport_stream_id: a number to identify the TS, chosen at will.
* @service: A _single_ service. Will be concatenated into the SDT.
* @program_num: The link between PAT, PMT and SDT.
* @program: A _single_ program with one or more streams associated with it.
* Will be concatenated into the PAT.
* @streams: A stream loop used to populate the PMT section for 'program'
* @encoders: A encoder loop. There must be one encoder for each stream.
* @next: Optionally chain this channel.
*/
struct vidtv_channel {
char *name;
u16 transport_stream_id;
struct vidtv_psi_table_sdt_service *service;
u16 program_num;
struct vidtv_psi_table_pat_program *program;
struct vidtv_psi_table_pmt_stream *streams;
struct vidtv_encoder *encoders;
struct vidtv_channel *next;
};
/**
* vidtv_channel_si_init - Init the PSI tables from the channels in the mux
* @m: The mux containing the channels.
*/
void vidtv_channel_si_init(struct vidtv_mux *m);
void vidtv_channel_si_destroy(struct vidtv_mux *m);
/**
* vidtv_channels_init - Init hardcoded, fake 'channels'.
* @m: The mux to store the channels into.
*/
void vidtv_channels_init(struct vidtv_mux *m);
struct vidtv_channel
*vidtv_channel_s302m_init(struct vidtv_channel *head, u16 transport_stream_id);
void vidtv_channels_destroy(struct vidtv_mux *m);
#endif //VIDTV_CHANNEL_H

View File

@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0
/*
* The Virtual DVB test driver 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.
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__
#include <linux/printk.h>
#include <linux/ratelimit.h>
#include <linux/string.h>
#include <linux/types.h>
#include "vidtv_common.h"
/**
* vidtv_memcpy() - wrapper routine to be used by MPEG-TS
* generator, in order to avoid going past the
* output buffer.
* @to: Starting element to where a MPEG-TS packet will
* be copied.
* @to_offset: Starting position of the @to buffer to be filled.
* @to_size: Size of the @to buffer.
* @from: Starting element of the buffer to be copied.
* @len: Number of elements to be copy from @from buffer
* into @to+ @to_offset buffer.
*
* Note:
* Real digital TV demod drivers should not have memcpy
* wrappers. We use it here because emulating MPEG-TS
* generation at kernelspace requires some extra care.
*
* Return:
* Returns the number of bytes written
*/
u32 vidtv_memcpy(void *to,
size_t to_offset,
size_t to_size,
const void *from,
size_t len)
{
if (unlikely(to_offset + len > to_size)) {
pr_err_ratelimited("overflow detected, skipping. Try increasing the buffer size. Needed %lu, had %zu\n",
to_offset + len,
to_size);
return 0;
}
memcpy(to + to_offset, from, len);
return len;
}
/**
* vidtv_memset() - wrapper routine to be used by MPEG-TS
* generator, in order to avoid going past the
* output buffer.
* @to: Starting element to set
* @to_offset: Starting position of the @to buffer to be filled.
* @to_size: Size of the @to buffer.
* @c: The value to set the memory to.
* @len: Number of elements to be copy from @from buffer
* into @to+ @to_offset buffer.
*
* Note:
* Real digital TV demod drivers should not have memset
* wrappers. We use it here because emulating MPEG-TS
* generation at kernelspace requires some extra care.
*
* Return:
* Returns the number of bytes written
*/
u32 vidtv_memset(void *to,
size_t to_offset,
size_t to_size,
const int c,
size_t len)
{
if (unlikely(to_offset + len > to_size)) {
pr_err_ratelimited("overflow detected, skipping. Try increasing the buffer size. Needed %lu, had %zu\n",
to_offset + len,
to_size);
return 0;
}
memset(to + to_offset, c, len);
return len;
}

View File

@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* The Virtual DVB test driver 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.
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#ifndef VIDTV_COMMON_H
#define VIDTV_COMMON_H
#include <linux/types.h>
#define CLOCK_UNIT_90KHZ 90000
#define CLOCK_UNIT_27MHZ 27000000
#define VIDTV_SLEEP_USECS 10000
#define VIDTV_MAX_SLEEP_USECS (2 * VIDTV_SLEEP_USECS)
#define VIDTV_DEFAULT_TS_ID 0x744
u32 vidtv_memcpy(void *to,
size_t to_offset,
size_t to_size,
const void *from,
size_t len);
u32 vidtv_memset(void *to,
size_t to_offset,
size_t to_size,
int c,
size_t len);
#endif // VIDTV_COMMON_H

View File

@@ -0,0 +1,96 @@
/* 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 a generic encoder type that can provide data for a stream
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#ifndef VIDTV_ENCODER_H
#define VIDTV_ENCODER_H
#include <linux/types.h>
enum vidtv_encoder_id {
/* add IDs here when implementing new encoders */
S302M,
};
struct vidtv_access_unit {
u32 num_samples;
u64 pts;
u64 dts;
u32 nbytes;
u32 offset;
struct vidtv_access_unit *next;
};
/**
* struct vidtv_encoder - A generic encoder type.
* @id: So we can cast to a concrete implementation when needed.
* @name: Usually the same as the stream name.
* @encoder_buf: The encoder internal buffer for the access units.
* @encoder_buf_sz: The encoder buffer size, in bytes
* @encoder_buf_offset: Our byte position in the encoder buffer.
* @sample_count: How many samples we have encoded in total.
* @src_buf: The source of raw data to be encoded, encoder might set a
* default if null.
* @src_buf_offset: Our position in the source buffer.
* @is_video_encoder: Whether this a video encoder (as opposed to audio)
* @ctx: Encoder-specific state.
* @stream_id: Examples: Audio streams (0xc0-0xdf), Video streams
* (0xe0-0xef).
* @es_id: The TS PID to use for the elementary stream in this encoder.
* @encode: Prepare enough AUs for the given amount of time.
* @clear: Clear the encoder output.
* @sync: Attempt to synchronize with this encoder.
* @sampling_rate_hz: The sampling rate (or fps, if video) used.
* @last_sample_cb: Called when the encoder runs out of data.This is
* so the source can read data in a
* piecemeal fashion instead of having to
* provide it all at once.
* @destroy: Destroy this encoder, freeing allocated resources.
* @next: Next in the chain
*/
struct vidtv_encoder {
enum vidtv_encoder_id id;
char *name;
u8 *encoder_buf;
u32 encoder_buf_sz;
u32 encoder_buf_offset;
u64 sample_count;
struct vidtv_access_unit *access_units;
void *src_buf;
u32 src_buf_sz;
u32 src_buf_offset;
bool is_video_encoder;
void *ctx;
__be16 stream_id;
__be16 es_pid;
void *(*encode)(struct vidtv_encoder *e, u64 elapsed_time_usecs);
u32 (*clear)(struct vidtv_encoder *e);
struct vidtv_encoder *sync;
u32 sampling_rate_hz;
void (*last_sample_cb)(u32 sample_no);
void (*destroy)(struct vidtv_encoder *e);
struct vidtv_encoder *next;
};
#endif /* VIDTV_ENCODER_H */

View File

@@ -0,0 +1,479 @@
// 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 multiplexer logic for TS packets from different
* elementary streams
*
* Loosely based on libavcodec/mpegtsenc.c
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/ratelimit.h>
#include <linux/delay.h>
#include <linux/vmalloc.h>
#include <linux/math64.h>
#include "vidtv_mux.h"
#include "vidtv_ts.h"
#include "vidtv_pes.h"
#include "vidtv_encoder.h"
#include "vidtv_channel.h"
#include "vidtv_common.h"
#include "vidtv_psi.h"
static struct vidtv_mux_pid_ctx
*vidtv_mux_get_pid_ctx(struct vidtv_mux *m, u16 pid)
{
struct vidtv_mux_pid_ctx *ctx;
hash_for_each_possible(m->pid_ctx, ctx, h, pid)
if (ctx->pid == pid)
return ctx;
return NULL;
}
static struct vidtv_mux_pid_ctx
*vidtv_mux_create_pid_ctx_once(struct vidtv_mux *m, u16 pid)
{
struct vidtv_mux_pid_ctx *ctx;
ctx = vidtv_mux_get_pid_ctx(m, pid);
if (ctx)
goto end;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
ctx->pid = pid;
ctx->cc = 0;
hash_add(m->pid_ctx, &ctx->h, pid);
end:
return ctx;
}
static void vidtv_mux_pid_ctx_init(struct vidtv_mux *m)
{
struct vidtv_psi_table_pat_program *p = m->si.pat->program;
u16 pid;
hash_init(m->pid_ctx);
/* push the pcr pid ctx */
vidtv_mux_create_pid_ctx_once(m, m->pcr_pid);
/* push the null packet pid ctx */
vidtv_mux_create_pid_ctx_once(m, TS_NULL_PACKET_PID);
/* push the PAT pid ctx */
vidtv_mux_create_pid_ctx_once(m, VIDTV_PAT_PID);
/* push the SDT pid ctx */
vidtv_mux_create_pid_ctx_once(m, VIDTV_SDT_PID);
/* add a ctx for all PMT sections */
while (p) {
pid = vidtv_psi_get_pat_program_pid(p);
vidtv_mux_create_pid_ctx_once(m, pid);
p = p->next;
}
}
static void vidtv_mux_pid_ctx_destroy(struct vidtv_mux *m)
{
int bkt;
struct vidtv_mux_pid_ctx *ctx;
struct hlist_node *tmp;
hash_for_each_safe(m->pid_ctx, bkt, tmp, ctx, h) {
hash_del(&ctx->h);
kfree(ctx);
}
}
static void vidtv_mux_update_clk(struct vidtv_mux *m)
{
/* call this at every thread iteration */
u64 elapsed_time;
/* this will not hold a value yet if we have just started */
m->timing.past_jiffies = m->timing.current_jiffies ?
m->timing.current_jiffies :
get_jiffies_64();
m->timing.current_jiffies = get_jiffies_64();
elapsed_time = jiffies_to_usecs(m->timing.current_jiffies -
m->timing.past_jiffies);
/* update the 27Mhz clock proportionally to the elapsed time */
m->timing.clk += (CLOCK_UNIT_27MHZ / USEC_PER_SEC) * elapsed_time;
}
static u32 vidtv_mux_push_si(struct vidtv_mux *m)
{
u32 initial_offset = m->mux_buf_offset;
struct vidtv_mux_pid_ctx *pat_ctx;
struct vidtv_mux_pid_ctx *pmt_ctx;
struct vidtv_mux_pid_ctx *sdt_ctx;
struct vidtv_psi_pat_write_args pat_args = {};
struct vidtv_psi_pmt_write_args pmt_args = {};
struct vidtv_psi_sdt_write_args sdt_args = {};
u32 nbytes; /* the number of bytes written by this function */
u16 pmt_pid;
u32 i;
pat_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_PAT_PID);
sdt_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_SDT_PID);
pat_args.buf = m->mux_buf;
pat_args.offset = m->mux_buf_offset;
pat_args.pat = m->si.pat;
pat_args.buf_sz = m->mux_buf_sz;
pat_args.continuity_counter = &pat_ctx->cc;
m->mux_buf_offset += vidtv_psi_pat_write_into(pat_args);
for (i = 0; i < m->si.pat->programs; ++i) {
pmt_pid = vidtv_psi_pmt_get_pid(m->si.pmt_secs[i],
m->si.pat);
if (pmt_pid > TS_LAST_VALID_PID) {
pr_warn_ratelimited("PID: %d not found\n", pmt_pid);
continue;
}
pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid);
pmt_args.buf = m->mux_buf;
pmt_args.offset = m->mux_buf_offset;
pmt_args.pmt = m->si.pmt_secs[i];
pmt_args.pid = pmt_pid;
pmt_args.buf_sz = m->mux_buf_sz;
pmt_args.continuity_counter = &pmt_ctx->cc;
pmt_args.pcr_pid = m->pcr_pid;
/* write each section into buffer */
m->mux_buf_offset += vidtv_psi_pmt_write_into(pmt_args);
}
sdt_args.buf = m->mux_buf;
sdt_args.offset = m->mux_buf_offset;
sdt_args.sdt = m->si.sdt;
sdt_args.buf_sz = m->mux_buf_sz;
sdt_args.continuity_counter = &sdt_ctx->cc;
m->mux_buf_offset += vidtv_psi_sdt_write_into(sdt_args);
nbytes = m->mux_buf_offset - initial_offset;
m->num_streamed_si++;
return nbytes;
}
static u32 vidtv_mux_push_pcr(struct vidtv_mux *m)
{
struct pcr_write_args args = {};
struct vidtv_mux_pid_ctx *ctx;
u32 nbytes = 0;
ctx = vidtv_mux_get_pid_ctx(m, m->pcr_pid);
args.dest_buf = m->mux_buf;
args.pid = m->pcr_pid;
args.buf_sz = m->mux_buf_sz;
args.continuity_counter = &ctx->cc;
/* the 27Mhz clock will feed both parts of the PCR bitfield */
args.pcr = m->timing.clk;
nbytes += vidtv_ts_pcr_write_into(args);
m->mux_buf_offset += nbytes;
m->num_streamed_pcr++;
return nbytes;
}
static bool vidtv_mux_should_push_pcr(struct vidtv_mux *m)
{
u64 next_pcr_at;
if (m->num_streamed_pcr == 0)
return true;
next_pcr_at = m->timing.start_jiffies +
usecs_to_jiffies(m->num_streamed_pcr *
m->timing.pcr_period_usecs);
return time_after64(m->timing.current_jiffies, next_pcr_at);
}
static bool vidtv_mux_should_push_si(struct vidtv_mux *m)
{
u64 next_si_at;
if (m->num_streamed_si == 0)
return true;
next_si_at = m->timing.start_jiffies +
usecs_to_jiffies(m->num_streamed_si *
m->timing.si_period_usecs);
return time_after64(m->timing.current_jiffies, next_si_at);
}
static u32 vidtv_mux_packetize_access_units(struct vidtv_mux *m,
struct vidtv_encoder *e)
{
u32 nbytes = 0;
struct pes_write_args args = {};
u32 initial_offset = m->mux_buf_offset;
struct vidtv_access_unit *au = e->access_units;
u8 *buf = NULL;
struct vidtv_mux_pid_ctx *pid_ctx = vidtv_mux_create_pid_ctx_once(m,
be16_to_cpu(e->es_pid));
args.dest_buf = m->mux_buf;
args.dest_buf_sz = m->mux_buf_sz;
args.pid = be16_to_cpu(e->es_pid);
args.encoder_id = e->id;
args.continuity_counter = &pid_ctx->cc;
args.stream_id = be16_to_cpu(e->stream_id);
args.send_pts = true;
while (au) {
buf = e->encoder_buf + au->offset;
args.from = buf;
args.access_unit_len = au->nbytes;
args.dest_offset = m->mux_buf_offset;
args.pts = au->pts;
m->mux_buf_offset += vidtv_pes_write_into(args);
au = au->next;
}
/*
* clear the encoder state once the ES data has been written to the mux
* buffer
*/
e->clear(e);
nbytes = m->mux_buf_offset - initial_offset;
return nbytes;
}
static u32 vidtv_mux_poll_encoders(struct vidtv_mux *m)
{
u32 nbytes = 0;
u32 au_nbytes;
struct vidtv_channel *cur_chnl = m->channels;
struct vidtv_encoder *e = NULL;
u64 elapsed_time_usecs = jiffies_to_usecs(m->timing.current_jiffies -
m->timing.past_jiffies);
elapsed_time_usecs = min_t(u64, elapsed_time_usecs, (u64)VIDTV_MAX_SLEEP_USECS);
while (cur_chnl) {
e = cur_chnl->encoders;
while (e) {
/* encode for 'elapsed_time_usecs' */
e->encode(e, elapsed_time_usecs);
/* get the TS packets into the mux buffer */
au_nbytes = vidtv_mux_packetize_access_units(m, e);
nbytes += au_nbytes;
m->mux_buf_offset += au_nbytes;
/* grab next encoder */
e = e->next;
}
/* grab the next channel */
cur_chnl = cur_chnl->next;
}
return nbytes;
}
static u32 vidtv_mux_pad_with_nulls(struct vidtv_mux *m, u32 npkts)
{
struct null_packet_write_args args = {};
u32 initial_offset = m->mux_buf_offset;
u32 nbytes; /* the number of bytes written by this function */
u32 i;
struct vidtv_mux_pid_ctx *ctx;
ctx = vidtv_mux_get_pid_ctx(m, TS_NULL_PACKET_PID);
args.dest_buf = m->mux_buf;
args.buf_sz = m->mux_buf_sz;
args.continuity_counter = &ctx->cc;
args.dest_offset = m->mux_buf_offset;
for (i = 0; i < npkts; ++i) {
m->mux_buf_offset += vidtv_ts_null_write_into(args);
args.dest_offset = m->mux_buf_offset;
}
nbytes = m->mux_buf_offset - initial_offset;
/* sanity check */
if (nbytes != npkts * TS_PACKET_LEN)
pr_err_ratelimited("%d != %d\n", nbytes, npkts * TS_PACKET_LEN);
return nbytes;
}
static u32 vidtv_mux_check_mux_rate(struct vidtv_mux *m)
{
/*
* attempt to maintain a constant mux rate, padding with null packets
* if needed
*/
u32 nbytes = 0; /* the number of bytes written by this function */
u64 nbytes_expected; /* the number of bytes we should have written */
u64 nbytes_streamed; /* the number of bytes we actually wrote */
u32 num_null_pkts; /* number of null packets to bridge the gap */
u64 elapsed_time_msecs = jiffies_to_usecs(m->timing.current_jiffies -
m->timing.past_jiffies);
elapsed_time_msecs = min(elapsed_time_msecs, (u64)VIDTV_MAX_SLEEP_USECS / 1000);
nbytes_expected = div64_u64(m->mux_rate_kbytes_sec * 1000, MSEC_PER_SEC);
nbytes_expected *= elapsed_time_msecs;
nbytes_streamed = m->mux_buf_offset;
if (nbytes_streamed < nbytes_expected) {
/* can't write half a packet: roundup to a 188 multiple */
nbytes_expected = roundup(nbytes_expected - nbytes_streamed, TS_PACKET_LEN);
num_null_pkts = nbytes_expected / TS_PACKET_LEN;
nbytes += vidtv_mux_pad_with_nulls(m, num_null_pkts);
}
return nbytes;
}
static void vidtv_mux_clear(struct vidtv_mux *m)
{
/* clear the packets currently in the mux */
memset(m->mux_buf, 0, m->mux_buf_sz * sizeof(*m->mux_buf));
/* point to the beginning of the buffer again */
m->mux_buf_offset = 0;
}
static void vidtv_mux_tick(struct work_struct *work)
{
struct vidtv_mux *m = container_of(work,
struct vidtv_mux,
mpeg_thread);
u32 nbytes;
u32 npkts;
while (m->streaming) {
nbytes = 0;
vidtv_mux_update_clk(m);
if (vidtv_mux_should_push_pcr(m))
nbytes += vidtv_mux_push_pcr(m);
if (vidtv_mux_should_push_si(m))
nbytes += vidtv_mux_push_si(m);
nbytes += vidtv_mux_poll_encoders(m);
nbytes += vidtv_mux_check_mux_rate(m);
npkts = nbytes / TS_PACKET_LEN;
/* if the buffer is not aligned there is a bug somewhere */
if (nbytes % TS_PACKET_LEN)
pr_err_ratelimited("Misaligned buffer\n");
if (m->on_new_packets_available_cb)
m->on_new_packets_available_cb(m->priv,
m->mux_buf,
npkts);
vidtv_mux_clear(m);
usleep_range(VIDTV_SLEEP_USECS, VIDTV_MAX_SLEEP_USECS);
}
}
void vidtv_mux_start_thread(struct vidtv_mux *m)
{
if (m->streaming) {
pr_warn_ratelimited("Already streaming. Skipping.\n");
return;
}
m->streaming = true;
m->timing.start_jiffies = get_jiffies_64();
schedule_work(&m->mpeg_thread);
}
void vidtv_mux_stop_thread(struct vidtv_mux *m)
{
if (m->streaming) {
m->streaming = false; /* thread will quit */
cancel_work_sync(&m->mpeg_thread);
}
}
struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args)
{
struct vidtv_mux *m = kzalloc(sizeof(*m), GFP_KERNEL);
m->timing.pcr_period_usecs = args.pcr_period_usecs;
m->timing.si_period_usecs = args.si_period_usecs;
m->mux_rate_kbytes_sec = args.mux_rate_kbytes_sec;
m->on_new_packets_available_cb = args.on_new_packets_available_cb;
m->mux_buf = vzalloc(args.mux_buf_sz);
m->mux_buf_sz = args.mux_buf_sz;
m->pcr_pid = args.pcr_pid;
m->transport_stream_id = args.transport_stream_id;
m->priv = args.priv;
if (args.channels)
m->channels = args.channels;
else
vidtv_channels_init(m);
/* will alloc data for pmt_sections after initializing pat */
vidtv_channel_si_init(m);
INIT_WORK(&m->mpeg_thread, vidtv_mux_tick);
vidtv_mux_pid_ctx_init(m);
return m;
}
void vidtv_mux_destroy(struct vidtv_mux *m)
{
vidtv_mux_stop_thread(m);
vidtv_mux_pid_ctx_destroy(m);
vidtv_channel_si_destroy(m);
vidtv_channels_destroy(m);
vfree(m->mux_buf);
kfree(m);
}

View File

@@ -0,0 +1,160 @@
/* 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 muxer logic for TS packets from different
* elementary streams.
*
* Loosely based on libavcodec/mpegtsenc.c
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#ifndef VIDTV_MUX_H
#define VIDTV_MUX_H
#include <linux/types.h>
#include <linux/hashtable.h>
#include <linux/workqueue.h>
#include "vidtv_psi.h"
/**
* struct vidtv_mux_timing - Timing related information
*
* This is used to decide when PCR or PSI packets should be sent. This will also
* provide storage for the clock, which is used to compute the value for the PCR.
*
* @start_jiffies: The value of 'jiffies' when we started the mux thread.
* @current_jiffies: The value of 'jiffies' for the current iteration.
* @past_jiffies: The value of 'jiffies' for the past iteration.
* @clk: A 27Mhz clock from which we will drive the PCR. Updated proportionally
* on every iteration.
* @pcr_period_usecs: How often we should send PCR packets.
* @si_period_usecs: How often we should send PSI packets.
*/
struct vidtv_mux_timing {
u64 start_jiffies;
u64 current_jiffies;
u64 past_jiffies;
u64 clk;
u64 pcr_period_usecs;
u64 si_period_usecs;
};
/**
* struct vidtv_mux_si - Store the PSI context.
*
* This is used to store the PAT, PMT sections and SDT in use by the muxer.
*
* The muxer acquire these by looking into the hardcoded channels in
* vidtv_channel and then periodically sends the TS packets for them>
*
* @pat: The PAT in use by the muxer.
* @pmt_secs: The PMT sections in use by the muxer. One for each program in the PAT.
* @sdt: The SDT in use by the muxer.
*/
struct vidtv_mux_si {
/* the SI tables */
struct vidtv_psi_table_pat *pat;
struct vidtv_psi_table_pmt **pmt_secs; /* the PMT sections */
struct vidtv_psi_table_sdt *sdt;
};
/**
* struct vidtv_mux_pid_ctx - Store the context for a given TS PID.
* @pid: The TS PID.
* @cc: The continuity counter for this PID. It is incremented on every TS
* pack and it will wrap around at 0xf0. If the decoder notices a sudden jump in
* this counter this will trigger a discontinuity state.
* @h: This is embedded in a hash table, mapping pid -> vidtv_mux_pid_ctx
*/
struct vidtv_mux_pid_ctx {
u16 pid;
u8 cc; /* continuity counter */
struct hlist_node h;
};
/**
* struct vidtv_mux - A muxer abstraction loosely based in libavcodec/mpegtsenc.c
* @mux_rate_kbytes_sec: The bit rate for the TS, in kbytes.
* @timing: Keeps track of timing related information.
* @pid_ctx: A hash table to keep track of per-PID metadata.
* @on_new_packets_available_cb: A callback to inform of new TS packets ready.
* @mux_buf: A pointer to a buffer for this muxer. TS packets are stored there
* and then passed on to the bridge driver.
* @mux_buf_sz: The size for 'mux_buf'.
* @mux_buf_offset: The current offset into 'mux_buf'.
* @channels: The channels associated with this muxer.
* @si: Keeps track of the PSI context.
* @num_streamed_pcr: Number of PCR packets streamed.
* @num_streamed_si: The number of PSI packets streamed.
* @mpeg_thread: Thread responsible for the muxer loop.
* @streaming: whether 'mpeg_thread' is running.
* @pcr_pid: The TS PID used for the PSI packets. All channels will share the
* same PCR.
* @transport_stream_id: The transport stream ID
* @priv: Private data.
*/
struct vidtv_mux {
struct vidtv_mux_timing timing;
u32 mux_rate_kbytes_sec;
DECLARE_HASHTABLE(pid_ctx, 3);
void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets);
u8 *mux_buf;
u32 mux_buf_sz;
u32 mux_buf_offset;
struct vidtv_channel *channels;
struct vidtv_mux_si si;
u64 num_streamed_pcr;
u64 num_streamed_si;
struct work_struct mpeg_thread;
bool streaming;
u16 pcr_pid;
u16 transport_stream_id;
void *priv;
};
/**
* struct vidtv_mux_init_args - Arguments used to inix the muxer.
* @mux_rate_kbytes_sec: The bit rate for the TS, in kbytes.
* @on_new_packets_available_cb: A callback to inform of new TS packets ready.
* @mux_buf_sz: The size for 'mux_buf'.
* @pcr_period_usecs: How often we should send PCR packets.
* @si_period_usecs: How often we should send PSI packets.
* @pcr_pid: The TS PID used for the PSI packets. All channels will share the
* same PCR.
* @transport_stream_id: The transport stream ID
* @channels: an optional list of channels to use
* @priv: Private data.
*/
struct vidtv_mux_init_args {
u32 mux_rate_kbytes_sec;
void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets);
u32 mux_buf_sz;
u64 pcr_period_usecs;
u64 si_period_usecs;
u16 pcr_pid;
u16 transport_stream_id;
struct vidtv_channel *channels;
void *priv;
};
struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args);
void vidtv_mux_destroy(struct vidtv_mux *m);
void vidtv_mux_start_thread(struct vidtv_mux *m);
void vidtv_mux_stop_thread(struct vidtv_mux *m);
#endif //VIDTV_MUX_H

View File

@@ -0,0 +1,398 @@
// 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 <asm/byteorder.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;
}
static u32 vidtv_pes_h_get_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_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;
#ifdef __BIG_ENDIAN
mask1 = GENMASK(30, 32);
mask2 = GENMASK(15, 29);
mask3 = GENMASK(0, 14);
#else
mask1 = GENMASK(32, 30);
mask2 = GENMASK(29, 15);
mask3 = GENMASK(14, 0);
#endif
/* 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 = args;
u32 stream_id = (args.encoder_id == S302M) ? PRIVATE_STREAM_1_ID : args.stream_id;
u16 pes_opt_bitfield = 0x2 << 13;
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.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_stuffing(void *dest_buf,
u32 dest_offset,
u32 n_stuffing_bytes,
u32 buf_sz)
{
u32 nbytes = 0;
struct vidtv_mpeg_ts_adaption ts_adap = {};
u32 stuff_nbytes = 0;
if (!n_stuffing_bytes)
goto out;
if (n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES) {
pr_warn_ratelimited("More than %d stuffing bytes for a PES packet!\n",
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,
buf_sz,
0,
n_stuffing_bytes);
goto out;
}
stuff_nbytes = n_stuffing_bytes - sizeof(ts_adap);
/* length _immediately_ following 'adaptation_field_length' */
ts_adap.length = sizeof(ts_adap) -
sizeof(ts_adap.length) +
stuff_nbytes;
/* write the adap after the TS header */
nbytes += vidtv_memcpy(dest_buf,
dest_offset + nbytes,
buf_sz,
&ts_adap,
sizeof(ts_adap));
/* write the stuffing bytes */
nbytes += vidtv_memset(dest_buf,
dest_offset + nbytes,
buf_sz,
TS_FILL_BYTE,
stuff_nbytes);
out:
if (nbytes != n_stuffing_bytes)
pr_warn_ratelimited("write size was %d, expected %d\n",
nbytes,
n_stuffing_bytes);
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 = {};
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.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 = {};
struct pes_header_write_args pes_header_args = {};
/* number of bytes written by this function */
u32 nbytes = 0;
u32 remaining_len = args.access_unit_len;
bool wrote_pes_header = false;
/* whether we need to stuff the TS packet to align the buffer */
bool should_insert_stuffing_bytes = false;
u32 available_space = 0;
u32 payload_write_len = 0;
u32 num_stuffing_bytes = 0;
if (!aligned) {
pr_warn_ratelimited("Cannot start a PES packet in a misaligned buffer\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 - nbytes_past_boundary);
aligned = true;
}
if (args.send_dts && !args.send_pts) {
pr_warn_ratelimited("forbidden value '01' for PTS_DTS flags\n");
args.send_pts = true;
args.pts = args.dts;
}
/* see SMPTE 302M clause 6.4 */
if (args.encoder_id == S302M) {
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, 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_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 stuffing bytes are needed to align the buffer */
should_insert_stuffing_bytes = remaining_len < available_space;
/*
* how much of the _actual_ payload should be written in this
* packet.
*/
payload_write_len = (should_insert_stuffing_bytes) ?
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.encoder_id = args.encoder_id;
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;
pes_header_args.access_unit_len = args.access_unit_len;
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_write_len);
args.from += payload_write_len;
remaining_len -= payload_write_len;
}
return nbytes;
}

View File

@@ -0,0 +1,189 @@
/* 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
*/
#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. Only possible for video. */
#define PES_START_CODE_PREFIX 0x001 /* 00 00 01 */
/* Used when sending PTS, but not DTS */
struct vidtv_pes_optional_pts {
u8 pts1;
__be16 pts2;
__be16 pts3;
} __packed;
/* Used when sending both PTS and DTS */
struct vidtv_pes_optional_pts_dts {
u8 pts1;
__be16 pts2;
__be16 pts3;
u8 dts1;
__be16 dts2;
__be16 dts3;
} __packed;
/* PES optional flags */
struct vidtv_pes_optional {
/*
* These flags show which components are actually
* present in the "optinal fields" in the optinal PES
* header and which are not
*
* u16 two:2; //0x2
* u16 PES_scrambling_control:2;
* u16 PES_priority:1;
* u16 data_alignment_indicator:1; // unused
* u16 copyright:1;
* u16 original_or_copy:1;
* u16 PTS_DTS:2;
* 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;
*/
__be16 bitfield;
u8 length;
} __packed;
/* The PES header */
struct vidtv_mpeg_pes {
__be32 bitfield; /* packet_start_code_prefix:24, stream_id: 8 */
/* after this field until the end of the PES data payload */
__be16 length;
struct vidtv_pes_optional optional[];
} __packed;
/**
* struct pes_header_write_args - Arguments to write a PES header.
* @dest_buf: The buffer to write into.
* @dest_offset: where to start writing in the dest_buffer.
* @dest_buf_sz: The size of the dest_buffer
* @encoder_id: Encoder id (see vidtv_encoder.h)
* @send_pts: Should we send PTS?
* @pts: PTS value to send.
* @send_dts: Should we send DTS?
* @dts: DTS value to send.
* @stream_id: The stream id to use. Ex: Audio streams (0xc0-0xdf), Video
* streams (0xe0-0xef).
* @n_pes_h_s_bytes: Padding bytes. Might be used by an encoder if needed, gets
* discarded by the decoder.
* @access_unit_len: The size of _one_ access unit (with any headers it might need)
*/
struct pes_header_write_args {
void *dest_buf;
u32 dest_offset;
u32 dest_buf_sz;
u32 encoder_id;
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;
u32 access_unit_len;
};
/**
* struct pes_ts_header_write_args - Arguments to write a TS header.
* @dest_buf: The buffer to write into.
* @dest_offset: where to start writing in the dest_buffer.
* @dest_buf_sz: The size of the dest_buffer
* @pid: The PID to use for the TS packets.
* @continuity_counter: Incremented on every new TS packet.
* @n_pes_h_s_bytes: Padding bytes. Might be used by an encoder if needed, gets
* discarded by the decoder.
*/
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 - Arguments for the packetizer.
* @dest_buf: The buffer to write into.
* @from: A pointer to the encoder buffer containing one access unit.
* @access_unit_len: The size of _one_ access unit (with any headers it might need)
* @dest_offset: where to start writing in the dest_buffer.
* @dest_buf_sz: The size of the dest_buffer
* @pid: The PID to use for the TS packets.
* @encoder_id: Encoder id (see vidtv_encoder.h)
* @continuity_counter: Incremented on every new TS packet.
* @stream_id: The stream id to use. Ex: Audio streams (0xc0-0xdf), Video
* streams (0xe0-0xef).
* @send_pts: Should we send PTS?
* @pts: PTS value to send.
* @send_dts: Should we send DTS?
* @dts: DTS value to send.
* @n_pes_h_s_bytes: Padding bytes. Might be used by an encoder if needed, gets
* discarded by the decoder.
*/
struct pes_write_args {
void *dest_buf;
void *from;
u32 access_unit_len;
u32 dest_offset;
u32 dest_buf_sz;
u16 pid;
u32 encoder_id;
u8 *continuity_counter;
u16 stream_id;
bool send_pts;
u64 pts;
bool send_dts;
u64 dts;
u32 n_pes_h_s_bytes;
};
/**
* vidtv_pes_write_into - Write a PES packet as MPEG-TS packets into a buffer.
* @args: The args to use when writing
*
* This function 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.
*
* The data is then written into the buffer pointed to by 'args.buf'
*
* Return: The number of bytes written into the buffer. This is usually NOT
* equal to the size of the access unit, since we need space for PES headers, TS headers
* and padding bytes, if any.
*/
u32 vidtv_pes_write_into(struct pes_write_args args);
#endif // VIDTV_PES_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
/* 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 code for an AES3 (also known as AES/EBU) encoder.
* It is based on EBU Tech 3250 and SMPTE 302M technical documents.
*
* This encoder currently supports 16bit AES3 subframes using 16bit signed
* integers.
*
* Note: AU stands for Access Unit, and AAU stands for Audio Access Unit
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#ifndef VIDTV_S302M_H
#define VIDTV_S302M_H
#include <linux/types.h>
#include <asm/byteorder.h>
#include "vidtv_encoder.h"
/* see SMPTE 302M 2007 clause 7.3 */
#define VIDTV_S302M_BUF_SZ 65024
/* see ETSI TS 102 154 v.1.2.1 clause 7.3.5 */
#define VIDTV_S302M_FORMAT_IDENTIFIER 0x42535344
/**
* struct vidtv_s302m_ctx - s302m encoder context.
* @enc: A pointer to the containing encoder structure.
* @frame_index: The current frame in a block
*/
struct vidtv_s302m_ctx {
struct vidtv_encoder *enc;
u32 frame_index;
};
/**
* struct vidtv_smpte_s302m_es - s302m MPEG Elementary Stream header.
*
* See SMPTE 302M 2007 table 1.
*/
struct vidtv_smpte_s302m_es {
/*
*
* audio_packet_size:16;
* num_channels:2;
* channel_identification:8;
* bits_per_sample:2; // 0x0 for 16bits
* zero:4;
*/
__be32 bitfield;
} __packed;
struct vidtv_s302m_frame_16 {
u8 data[5];
} __packed;
/**
* struct vidtv_s302m_encoder_init_args - Args for the s302m encoder.
*
* @name: A name to identify this particular instance
* @src_buf: The source buffer, encoder will default to a sine wave if this is NULL.
* @src_buf_sz: The size of the source buffer.
* @es_pid: The MPEG Elementary Stream PID to use.
* @sync: Attempt to synchronize audio with this video encoder, if not NULL.
* @last_sample_cb: A callback called when the encoder runs out of data.
* @head: Add to this chain
*/
struct vidtv_s302m_encoder_init_args {
char *name;
void *src_buf;
u32 src_buf_sz;
u16 es_pid;
struct vidtv_encoder *sync;
void (*last_sample_cb)(u32 sample_no);
struct vidtv_encoder *head;
};
struct vidtv_encoder
*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args);
void vidtv_s302m_encoder_destroy(struct vidtv_encoder *encoder);
#endif /* VIDTV_S302M_H */

View File

@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0
/*
* The Virtual DVB test driver 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.
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__
#include <linux/printk.h>
#include <linux/ratelimit.h>
#include <linux/types.h>
#include <linux/math64.h>
#include <asm/byteorder.h>
#include "vidtv_ts.h"
#include "vidtv_common.h"
static u32 vidtv_ts_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;
}
void vidtv_ts_inc_cc(u8 *continuity_counter)
{
++*continuity_counter;
if (*continuity_counter > TS_CC_MAX_VAL)
*continuity_counter = 0;
}
u32 vidtv_ts_null_write_into(struct null_packet_write_args args)
{
u32 nbytes = 0;
struct vidtv_mpeg_ts ts_header = {};
ts_header.sync_byte = TS_SYNC_BYTE;
ts_header.bitfield = cpu_to_be16(TS_NULL_PACKET_PID);
ts_header.payload = 1;
ts_header.continuity_counter = *args.continuity_counter;
/* copy TS header */
nbytes += vidtv_memcpy(args.dest_buf,
args.dest_offset + nbytes,
args.buf_sz,
&ts_header,
sizeof(ts_header));
vidtv_ts_inc_cc(args.continuity_counter);
/* fill the rest with empty data */
nbytes += vidtv_memset(args.dest_buf,
args.dest_offset + nbytes,
args.buf_sz,
TS_FILL_BYTE,
TS_PACKET_LEN - nbytes);
/* we should have written exactly _one_ 188byte packet */
if (nbytes != TS_PACKET_LEN)
pr_warn_ratelimited("Expected exactly %d bytes, got %d\n",
TS_PACKET_LEN,
nbytes);
return nbytes;
}
u32 vidtv_ts_pcr_write_into(struct pcr_write_args args)
{
u32 nbytes = 0;
struct vidtv_mpeg_ts ts_header = {};
struct vidtv_mpeg_ts_adaption ts_adap = {};
ts_header.sync_byte = TS_SYNC_BYTE;
ts_header.bitfield = cpu_to_be16(args.pid);
ts_header.scrambling = 0;
/* cc is not incremented, but it is needed. see 13818-1 clause 2.4.3.3 */
ts_header.continuity_counter = *args.continuity_counter;
ts_header.payload = 0;
ts_header.adaptation_field = 1;
/* 13818-1 clause 2.4.3.5 */
ts_adap.length = 183;
ts_adap.PCR = 1;
/* copy TS header */
nbytes += vidtv_memcpy(args.dest_buf,
args.dest_offset + nbytes,
args.buf_sz,
&ts_header,
sizeof(ts_header));
/* write the adap after the TS header */
nbytes += vidtv_memcpy(args.dest_buf,
args.dest_offset + nbytes,
args.buf_sz,
&ts_adap,
sizeof(ts_adap));
/* write the PCR optional */
nbytes += vidtv_ts_write_pcr_bits(args.dest_buf,
args.dest_offset + nbytes,
args.pcr);
nbytes += vidtv_memset(args.dest_buf,
args.dest_offset + nbytes,
args.buf_sz,
TS_FILL_BYTE,
TS_PACKET_LEN - nbytes);
/* we should have written exactly _one_ 188byte packet */
if (nbytes != TS_PACKET_LEN)
pr_warn_ratelimited("Expected exactly %d bytes, got %d\n",
TS_PACKET_LEN,
nbytes);
return nbytes;
}

View File

@@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* The Virtual DVB test driver 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.
*
* Copyright (C) 2020 Daniel W. S. Almeida
*/
#ifndef VIDTV_TS_H
#define VIDTV_TS_H
#include <linux/types.h>
#include <asm/byteorder.h>
#define TS_SYNC_BYTE 0x47
#define TS_PACKET_LEN 188
#define TS_PAYLOAD_LEN 184
#define TS_NULL_PACKET_PID 0x1fff
#define TS_CC_MAX_VAL 0x0f /* 4 bits */
#define TS_LAST_VALID_PID 8191
#define TS_FILL_BYTE 0xff /* the byte used in packet stuffing */
struct vidtv_mpeg_ts_adaption {
u8 length;
struct {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u8 extension:1;
u8 private_data:1;
u8 splicing_point:1;
u8 OPCR:1;
u8 PCR:1;
u8 priority:1;
u8 random_access:1;
u8 discontinued:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
u8 discontinued:1;
u8 random_access:1;
u8 priority:1;
u8 PCR:1;
u8 OPCR:1;
u8 splicing_point:1;
u8 private_data:1;
u8 extension:1;
#else
#error "Unknown bitfield ordering"
#endif
} __packed;
u8 data[];
} __packed;
struct vidtv_mpeg_ts {
u8 sync_byte;
__be16 bitfield; /* tei: 1, payload_start:1 priority: 1, pid:13 */
struct {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u8 continuity_counter:4;
u8 payload:1;
u8 adaptation_field:1;
u8 scrambling:2;
#elif defined(__BIG_ENDIAN_BITFIELD)
u8 scrambling:2;
u8 adaptation_field:1;
u8 payload:1;
u8 continuity_counter:4;
#else
#error "Unknown bitfield ordering"
#endif
} __packed;
struct vidtv_mpeg_ts_adaption adaption[];
} __packed;
/**
* struct pcr_write_args - Arguments for the pcr_write_into function.
* @dest_buf: The buffer to write into.
* @dest_offset: The byte offset into the buffer.
* @pid: The TS PID for the PCR packets.
* @buf_sz: The size of the buffer in bytes.
* @countinuity_counter: The TS continuity_counter.
* @pcr: A sample from the system clock.
*/
struct pcr_write_args {
void *dest_buf;
u32 dest_offset;
u16 pid;
u32 buf_sz;
u8 *continuity_counter;
u64 pcr;
};
/**
* struct null_packet_write_args - Arguments for the null_write_into function
* @dest_buf: The buffer to write into.
* @dest_offset: The byte offset into the buffer.
* @buf_sz: The size of the buffer in bytes.
* @countinuity_counter: The TS continuity_counter.
*/
struct null_packet_write_args {
void *dest_buf;
u32 dest_offset;
u32 buf_sz;
u8 *continuity_counter;
};
/* Increment the continuity counter */
void vidtv_ts_inc_cc(u8 *continuity_counter);
/**
* vidtv_ts_null_write_into - Write a TS null packet into a buffer.
* @args: the arguments to use when writing.
*
* This function will write a null packet into a buffer. This is usually used to
* pad TS streams.
*
* Return: The number of bytes written into the buffer.
*/
u32 vidtv_ts_null_write_into(struct null_packet_write_args args);
/**
* vidtv_ts_pcr_write_into - Write a PCR packet into a buffer.
* @args: the arguments to use when writing.
*
* This function will write a PCR packet into a buffer. This is used to
* synchronize the clocks between encoders and decoders.
*
* Return: The number of bytes written into the buffer.
*/
u32 vidtv_ts_pcr_write_into(struct pcr_write_args args);
#endif //VIDTV_TS_H