Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Daniel W. S. Almeida | 1864 | 79.90% | 4 | 19.05% |
Mauro Carvalho Chehab | 469 | 20.10% | 17 | 80.95% |
Total | 2333 | 21 |
// 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 */ #include <linux/delay.h> #include <linux/dev_printk.h> #include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/math64.h> #include <linux/ratelimit.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/vmalloc.h> #include "vidtv_channel.h" #include "vidtv_common.h" #include "vidtv_encoder.h" #include "vidtv_mux.h" #include "vidtv_pes.h" #include "vidtv_psi.h" #include "vidtv_ts.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) return ctx; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return NULL; ctx->pid = pid; ctx->cc = 0; hash_add(m->pid_ctx, &ctx->h, pid); return ctx; } static void vidtv_mux_pid_ctx_destroy(struct vidtv_mux *m) { struct vidtv_mux_pid_ctx *ctx; struct hlist_node *tmp; int bkt; hash_for_each_safe(m->pid_ctx, bkt, tmp, ctx, h) { hash_del(&ctx->h); kfree(ctx); } } static int 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 */ if (!vidtv_mux_create_pid_ctx_once(m, m->pcr_pid)) return -ENOMEM; /* push the NULL packet pid ctx */ if (!vidtv_mux_create_pid_ctx_once(m, TS_NULL_PACKET_PID)) goto free; /* push the PAT pid ctx */ if (!vidtv_mux_create_pid_ctx_once(m, VIDTV_PAT_PID)) goto free; /* push the SDT pid ctx */ if (!vidtv_mux_create_pid_ctx_once(m, VIDTV_SDT_PID)) goto free; /* push the NIT pid ctx */ if (!vidtv_mux_create_pid_ctx_once(m, VIDTV_NIT_PID)) goto free; /* push the EIT pid ctx */ if (!vidtv_mux_create_pid_ctx_once(m, VIDTV_EIT_PID)) goto free; /* 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; } return 0; free: vidtv_mux_pid_ctx_destroy(m); return -ENOMEM; } static void vidtv_mux_update_clk(struct vidtv_mux *m) { /* call this at every thread iteration */ u64 elapsed_time; m->timing.past_jiffies = m->timing.current_jiffies; 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) { struct vidtv_psi_pat_write_args pat_args = { .buf = m->mux_buf, .buf_sz = m->mux_buf_sz, .pat = m->si.pat, }; struct vidtv_psi_pmt_write_args pmt_args = { .buf = m->mux_buf, .buf_sz = m->mux_buf_sz, .pcr_pid = m->pcr_pid, }; struct vidtv_psi_sdt_write_args sdt_args = { .buf = m->mux_buf, .buf_sz = m->mux_buf_sz, .sdt = m->si.sdt, }; struct vidtv_psi_nit_write_args nit_args = { .buf = m->mux_buf, .buf_sz = m->mux_buf_sz, .nit = m->si.nit, }; struct vidtv_psi_eit_write_args eit_args = { .buf = m->mux_buf, .buf_sz = m->mux_buf_sz, .eit = m->si.eit, }; 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_mux_pid_ctx *nit_ctx; struct vidtv_mux_pid_ctx *eit_ctx; u32 nbytes; 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); nit_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_NIT_PID); eit_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_EIT_PID); pat_args.offset = m->mux_buf_offset; 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->num_pmt; ++i) { pmt_pid = vidtv_psi_pmt_get_pid(m->si.pmt_secs[i], m->si.pat); if (pmt_pid > TS_LAST_VALID_PID) { dev_warn_ratelimited(m->dev, "PID: %d not found\n", pmt_pid); continue; } pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid); pmt_args.offset = m->mux_buf_offset; pmt_args.pmt = m->si.pmt_secs[i]; pmt_args.pid = pmt_pid; pmt_args.continuity_counter = &pmt_ctx->cc; /* write each section into buffer */ m->mux_buf_offset += vidtv_psi_pmt_write_into(&pmt_args); } sdt_args.offset = m->mux_buf_offset; sdt_args.continuity_counter = &sdt_ctx->cc; m->mux_buf_offset += vidtv_psi_sdt_write_into(&sdt_args); nit_args.offset = m->mux_buf_offset; nit_args.continuity_counter = &nit_ctx->cc; m->mux_buf_offset += vidtv_psi_nit_write_into(&nit_args); eit_args.offset = m->mux_buf_offset; eit_args.continuity_counter = &eit_ctx->cc; m->mux_buf_offset += vidtv_psi_eit_write_into(&eit_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) { struct pes_write_args args = { .dest_buf = m->mux_buf, .dest_buf_sz = m->mux_buf_sz, .pid = be16_to_cpu(e->es_pid), .encoder_id = e->id, .stream_id = be16_to_cpu(e->stream_id), .send_pts = true, /* forbidden value '01'... */ .send_dts = false, /* ...for PTS_DTS flags */ }; struct vidtv_access_unit *au = e->access_units; u32 initial_offset = m->mux_buf_offset; struct vidtv_mux_pid_ctx *pid_ctx; u32 nbytes = 0; u8 *buf = NULL; /* see SMPTE 302M clause 6.4 */ if (args.encoder_id == S302M) { args.send_dts = false; args.send_pts = true; } pid_ctx = vidtv_mux_create_pid_ctx_once(m, be16_to_cpu(e->es_pid)); args.continuity_counter = &pid_ctx->cc; 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; args.pcr = m->timing.clk; 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) { struct vidtv_channel *cur_chnl = m->channels; struct vidtv_encoder *e = NULL; u32 nbytes = 0; u32 au_nbytes; while (cur_chnl) { e = cur_chnl->encoders; while (e) { e->encode(e); /* 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 = { .dest_buf = m->mux_buf, .buf_sz = m->mux_buf_sz, .dest_offset = m->mux_buf_offset, }; u32 initial_offset = m->mux_buf_offset; struct vidtv_mux_pid_ctx *ctx; u32 nbytes; u32 i; ctx = vidtv_mux_get_pid_ctx(m, TS_NULL_PACKET_PID); args.continuity_counter = &ctx->cc; 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) dev_err_ratelimited(m->dev, "%d != %d\n", nbytes, npkts * TS_PACKET_LEN); 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; } #define ERR_RATE 10000000 static void vidtv_mux_tick(struct work_struct *work) { struct vidtv_mux *m = container_of(work, struct vidtv_mux, mpeg_thread); struct dtv_frontend_properties *c = &m->fe->dtv_property_cache; u32 tot_bits = 0; 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_pad_with_nulls(m, 256); npkts = nbytes / TS_PACKET_LEN; /* if the buffer is not aligned there is a bug somewhere */ if (nbytes % TS_PACKET_LEN) dev_err_ratelimited(m->dev, "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); /* * Update bytes and packet counts at DVBv5 stats * * For now, both pre and post bit counts are identical, * but post BER count can be lower than pre BER, if the error * correction logic discards packages. */ c->pre_bit_count.stat[0].uvalue = nbytes * 8; c->post_bit_count.stat[0].uvalue = nbytes * 8; c->block_count.stat[0].uvalue += npkts; /* * Even without any visible errors for the user, the pre-BER * stats usually have an error range up to 1E-6. So, * add some random error increment count to it. * * Please notice that this is a poor guy's implementation, * as it will produce one corrected bit error every time * ceil(total bytes / ERR_RATE) is incremented, without * any sort of (pseudo-)randomness. */ tot_bits += nbytes * 8; if (tot_bits > ERR_RATE) { c->pre_bit_error.stat[0].uvalue++; tot_bits -= ERR_RATE; } usleep_range(VIDTV_SLEEP_USECS, VIDTV_MAX_SLEEP_USECS); } } void vidtv_mux_start_thread(struct vidtv_mux *m) { if (m->streaming) { dev_warn_ratelimited(m->dev, "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 dvb_frontend *fe, struct device *dev, struct vidtv_mux_init_args *args) { struct vidtv_mux *m; m = kzalloc(sizeof(*m), GFP_KERNEL); if (!m) return NULL; m->dev = dev; m->fe = fe; 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); if (!m->mux_buf) goto free_mux; 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; m->network_id = args->network_id; m->network_name = kstrdup(args->network_name, GFP_KERNEL); m->timing.current_jiffies = get_jiffies_64(); if (args->channels) m->channels = args->channels; else if (vidtv_channels_init(m) < 0) goto free_mux_buf; /* will alloc data for pmt_sections after initializing pat */ if (vidtv_channel_si_init(m) < 0) goto free_channels; INIT_WORK(&m->mpeg_thread, vidtv_mux_tick); if (vidtv_mux_pid_ctx_init(m) < 0) goto free_channel_si; return m; free_channel_si: vidtv_channel_si_destroy(m); free_channels: vidtv_channels_destroy(m); free_mux_buf: vfree(m->mux_buf); free_mux: kfree(m); return NULL; } 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); kfree(m->network_name); vfree(m->mux_buf); kfree(m); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1