Pull to refresh

Разработка ПО принимающего транспортный поток cо спутника

Доброго времени суток.
Недавно у меня возникла такая проблема: есть карта NetUP Dual DVB-S2-CI для приёма спутникового сигнала и неделя на то, чтобы написать ПО для получения всего транспортного потока с неё. При этом использовать готовое стороннее ПО нельзя. Ниже описывается как удалось решить эту проблему.

Изучение платы


image

С программированием DVB плат я столкнулся впервые. При этом никакого CD с ПО и документацией к карте не прилагалось. Поэтому я решил сначала изучить все характеристики карты. Вышел на сайт производителя www.netup.tv, информация оказалась весьма скудной:

NetUP Dual DVB-S2-CI
Профессиональная карта для приема спутникового сигнала

NetUP Dual DVB-S2-CI – это профессиональная карта для приема спутникового сигнала. Может применяться в шлюзах DVB-IP, домашних кинотеатрах, системах спутникового интернета и других решениях. По сравнению со стандартными DVB-S картами, NetUP Dual DVB-S2-CI занимает в 4 раза меньше места на сервере – 2 входа DVB-S/S2 и 2 слота Common Interface (CI) размещены на одной плате и занимают 1 PCE-e слот. Высокая производительность карты позволяет сохранить свободное место в серверах, что особенно ценно в системах с ограниченным количеством свободных слотов. К примеру, стандартный сервер высотой 1U с двумя слотами PCI-e способен принимать и декодировать контент с 4 транспондеров одновременно: NetUP IPTV Combine 4x или DVB-IP шлюз 4x.

Характеристики:
2 входа DVB-S/DVB-S2 позволяют принимать контент с двух транспондеров одновременно;
2 CI-слота для дешифрации двух потоков;
Поддерживается профессиональные CA-модули (PowerCAM Pro, Aston Pro Solutions, и другие);
PCE-e x1;
DiSEqC 2.x;
Драйвера для операционной системы Linux.
Поддерживаемые типы модуляции: DVB-S QPSK; DVB-S2 QPSK и 8PSK;
Опционально: 16APSK и 32APSK.
Награды: карта была награждена журналом «TELE-satellite», как «Лучшая карта для PC, которую мы когда-либо тестировали». Более подробную информацию можно узнать из статьи о тестировании NetUP Dual DVB-S2-CI.


Получается, что драйвера для этой карты существуют только для ОС Linux. Значит ПО должно быть написано под эту ОС. Удалось узнать, что существует команда разработчиков, которая пишет и отлаживает драйвера для DVB. Вышел на их сайт www.linuxtv.org, где узнал, что данная DVB карта поддерживается и начиная с ядра Linux версии 2.6.32.хх драйвера входят в состав ядра и устанавливать их дополнительно не нужно.

Реализация


1. Установка DVB карты в слот PCI Express

2. Выбор и установка ОС
Драйверам требовался Linux с ядром выше 2.6.32.хх. Графическая среда не важна, поэтому, для удобства написания и отладки приложения Qt лучше использовать KDE. Стабильная и хорошо сопровождаемая сборка на сегодняшний день – это Kubuntu. Установка прошла быстро (минут 15-20) и хорошо.

3. Проверяем видит ли ядро новое устройство
Используем команду dmesg | grep NetUP.
Видит. Теперь проверяем наличие интерфейсов устройств, которые должны располагаться в системном каталоге /dev/. Они находятся по пути: /dev/dvb/adapter0/, в котором находилось три файла: frontend, dmx, dvr. Для чего так много пока не понятно, но теперь нужно расшарить права доступа к ним иначе просто не получиться открыть эти интерфейсы. Проще это сделать, добавив текущего пользователя в группу этого устройства (video).

4. Проверяем работу устройства
Два этапа.
Сначала настраиваем DVB на частоту через консольную утилиту szap (man szap), она с третьего раза захватывает частоту! В нашем случае это означает что устройство работает и с ним можно «общаться».
Теперь тестируем приём данных с карты. Для этого нужно найти ПО считывающее ТВ или интернет трафик с карты. Находим GUI приложение для просмотра видео Xine (мощный проигрыватель видео для Linux), спустя примерно 10 секунд удаётся получить передаваемый ТВ-сигнал хорошего качества.

5. Изучаем особенности работы с драйвером DVB карты
Задача оказалась не тривиальной. После долгого поиска информации (к сожалению только на английском языке) и изучения данного вопроса удалось получить достаточно материала для написания программы:

Для драйвера DVB карта представляется в таком логическом виде:

image

Для доступа к API необходимо в программе заинклудить нужные из схемы устройства. Пример для Frontend:

#include <linux/dvb/frontend.h>

В этих исходниках содержаться все команды для работы с ним.

Алгоритм следующий:
а. Настраивается Frontend. Это устройство настраивается на заданную частоту с заданной символьной скоростью, инверсией. Затем передаёт полученный после демодуляции данные демультиплексору. Здесь получить все данные (которые нам нужны), к сожалению, не возможно.

б. Настраивается Demultiplexor. Он оперирует фильтрами и их у него два вида: секционный и PES. С этого устройства можно получить данные, но только настроив эти фильтры. Самое интересное в том, что чтобы прочитать все данные, нужно PES-фильтр настроить на маску 8192 и направить его выход в логическое DVR устройство. Отмечу, что об этом не сказано в официальной документации и на форумах. Я узнал это, покопавшись в исходниках DVBStream.

в. Настраивается DVR. На самом деле оказалось, что это файл, который постоянно пополняется данными от DMX (Demultiplexor) и из которого в итоге получаются данные для программы.

6. Выбираем SDK для написания ПО под Linux, устонвка, реализация
Qt – позволяет создавать ПО для многих ОС, удобен и очень понятен для изучения и главное, в нём уже реализовано много современных технологий (базы данных, интернет, кодировки, сигналы и слоты, GUI, 3D, скрипты и другие). Поэтому выбираем его. Установил Qt, написал приложение, отладил, всё прекрасно заработало.

Исходники


Приведу исходники реализации класса работы с устройством.
Заголовочный файл:

#ifndef ADVBSDevice_H
#define ADVBSDevice_H

#include class ADVBSDevice : public QObject
{
Q_OBJECT

private:
#define FRONT "/dev/dvb/adapter0/frontend0"
#define DMX "/dev/dvb/adapter0/demux0"
#define DVR "/dev/dvb/adapter0/dvr0"

int _front;
int _demux;
int _dvr;
int _freq;
int _srate;

enum { DMX_ALL_PIDS = 8192, /* PES фильтр пропускает весь TS поток */
/*EOVERFLOW = 75, Код ошибки, означает: Value too large for defined data type */
MAX_ATTEMPT = 512 };

bool tuneDvr ();
bool tuneDemux ();
bool tuneFrontend ();
bool isFrontendTuned ();

public:
explicit ADVBSDevice(QObject *parent = 0);
~ADVBSDevice();

bool init();
void stop();
int readBlock(const char *, int);

void setFrecuency(int freq) { _freq = freq * 1000; }
void setSymbolRate(int srate) { _srate = srate * 1000; }

signals:
void message(const QString &);
};

#endif // ADVBSDevice_H


Файл *.cpp:

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include <sys/poll.h>

#include "abvbsdevice.h"

ADVBSDevice::ADVBSDevice(QObject *parent) : QObject(parent)
{
_freq = -1;
_srate = -1;
_front = -1;
_demux = -1;
_dvr = -1;
}

ADVBSDevice::~ADVBSDevice()
{
stop();
}

bool ADVBSDevice::init()
{
if ((0 > _freq) || (0 > _srate)){
emit message(QString(tr("First set frecuensy and symbol rate!")));
return 0;
}

if ( tuneDvr() &&
tuneDemux() &&
tuneFrontend()
)
return true;

stop();
return false;
}

void ADVBSDevice::stop()
{
if (0 < _front) close(_front);
if (0 < _demux) { ioctl(_demux, DMX_STOP); close(_demux); }
if (0 < _dvr) close(_dvr);

_freq = -1;
_srate = -1;
_front = -1;
_demux = -1;
_dvr = -1;
}

int ADVBSDevice::readBlock(const char *buffer, int bufLen)
{
// return 0 - false, else - readed bytes.
if (0 > _dvr) {
emit message(QString(tr("First call: ADVBSDevice->init()!")));
return 0;
}

int readedBytes = 0;
int attempt = MAX_ATTEMPT;
pollfd pfd;
pfd.fd = _dvr;
pfd.events = POLLIN;

do
{
if (poll(&pfd ,1, 1))
{
if (pfd.revents & POLLIN)
{
if (-1 == (readedBytes = read(_dvr, (void*) buffer, bufLen)))
{
emit message(QString(tr("No data in device.")));
return 0;
}
}
}
}
while (--attempt && !readedBytes);

if (!attempt) {
emit message(QString(tr("No poll in device.")));
return 0;
}

return readedBytes;
}

bool ADVBSDevice::isFrontendTuned()
{
quint32 num32 = 0;
quint32 max32 = 0;
max32 = ~max32;
if (ioctl (_front, FE_READ_BER, &num32) >= 0) {
if (num32 > 0)
num32 = num32 / max32 * 100;
else num32 = 0;
emit message(QString("%1 %2%").arg(tr("Bit error rate:")).arg(num32));
}

num32 = 0;
if (ioctl (_front, FE_READ_UNCORRECTED_BLOCKS, &num32) >= 0) {
if (num32 > 0)
num32 = num32 / max32 * 100;
else num32 = 0;
emit message(QString("%1 %2%").arg(tr("Uncorrected blcks:")).arg(num32));
}

quint16 num16 = 0;
quint16 max16 = 0;
max16 = ~ max16;
if (ioctl (_front, FE_READ_SIGNAL_STRENGTH, &num16) >= 0) {
if (num16 > 0)
num16 = num16 / max16 * 100;
else num16 = 0;
emit message(QString("%1 %2%").arg(tr("Signal strength:")).arg(num16));
}

num16 = 0;
if (ioctl(_front, FE_READ_SNR, &num16) >= 0) {
if (num16 > 0)
num16 = num16 / max16 * 100;
else num16 = 0;
emit message(QString("%1 %2%").arg(tr("Signal/Noise Ratio:")).arg(num16));
}

fe_status_t status;
memset(&status, 0, sizeof(fe_status_t));

if (ioctl (_front, FE_READ_STATUS, &status) < 0){
emit message(QString(tr("Can't read status from frontend")));
return false;
}

if (!(status & FE_HAS_LOCK) || (status & FE_TIMEDOUT)) {
emit message(QString(tr("Can't lock frontend.")));
return false;
}

emit message(QString(tr("Frontend status:")));

if (status & FE_HAS_SIGNAL) emit message(QString(tr("Found something above the noise level.")));
if (status & FE_HAS_CARRIER) emit message(QString(tr("Found a DVB signal.")));
if (status & FE_HAS_VITERBI) emit message(QString(tr("FEC (code rate) is stable.")));
if (status & FE_HAS_SYNC) emit message(QString(tr("Found sync bytes.")));
if (status & FE_HAS_LOCK) emit message(QString(tr("Everything's working.")));
if (status & FE_TIMEDOUT) emit message(QString(tr("No lock within the last ~2 seconds.")));
if (status & FE_REINIT) emit message(QString(tr("Recommended to reset DiSEqC, tone and parameters.")));

// dvb_frontend_info info;
// if (ioctl (_front, FE_GET_INFO, &info) >= 0)
// {
// emit message(QString("Frontend info:"));
// emit message(QString("Name: %1.").arg(info.name));

// if (info.type == FE_QPSK) emit message(QString("Type: SAT Card."));
// if (info.type == FE_QAM) emit message(QString("Type: CAB Card."));
// if (info.type == FE_OFDM) emit message(QString("Type: TER Card."));
// if (info.type == FE_ATSC) emit message(QString("Type: US Card."));

// emit message(QString("Frequency min: %1.").arg(info.frequency_min));
// emit message(QString("Frequency max: %1.").arg(info.frequency_max));
// emit message(QString("Frequency step size: %1.").arg(info.frequency_stepsize));
// emit message(QString("Frequency tolerance: %1.").arg(info.frequency_tolerance));
// emit message(QString("Symbol rate min: %1.").arg(info.symbol_rate_min));
// emit message(QString("Symbol rate max: %1.").arg(info.symbol_rate_max));
// emit message(QString("Symbol rate tolerance: %1.").arg(info.symbol_rate_tolerance));
// emit message(QString("Notifier delay: %1.").arg(info.notifier_delay));

// emit message(QString("Capabiletes of card:"));
// if (info.caps & FE_CAN_INVERSION_AUTO) emit message(QString("FE_CAN_INVERSION_AUTO"));
// if (info.caps & FE_CAN_FEC_1_2) emit message(QString("FE_CAN_FEC_1_2"));
// if (info.caps & FE_CAN_FEC_2_3) emit message(QString("FE_CAN_FEC_2_3"));
// if (info.caps & FE_CAN_FEC_3_4) emit message(QString("FE_CAN_FEC_3_4"));
// if (info.caps & FE_CAN_FEC_4_5) emit message(QString("FE_CAN_FEC_4_5"));
// if (info.caps & FE_CAN_FEC_5_6) emit message(QString("FE_CAN_FEC_5_6"));
// if (info.caps & FE_CAN_FEC_6_7) emit message(QString("FE_CAN_FEC_6_7"));
// if (info.caps & FE_CAN_FEC_7_8) emit message(QString("FE_CAN_FEC_7_8"));
// if (info.caps & FE_CAN_FEC_8_9) emit message(QString("FE_CAN_FEC_8_9"));
// if (info.caps & FE_CAN_FEC_AUTO) emit message(QString("FE_CAN_FEC_AUTO"));
// if (info.caps & FE_CAN_QPSK) emit message(QString("FE_CAN_QPSK"));
// if (info.caps & FE_CAN_QAM_16) emit message(QString("FE_CAN_QAM_16"));
// if (info.caps & FE_CAN_QAM_32) emit message(QString("FE_CAN_QAM_32"));
// if (info.caps & FE_CAN_QAM_64) emit message(QString("FE_CAN_QAM_64"));
// if (info.caps & FE_CAN_QAM_128) emit message(QString("FE_CAN_QAM_128"));
// if (info.caps & FE_CAN_QAM_256) emit message(QString("FE_CAN_QAM_256"));
// if (info.caps & FE_CAN_QAM_AUTO) emit message(QString("FE_CAN_QAM_AUTO"));
// if (info.caps & FE_CAN_TRANSMISSION_MODE_AUTO) emit message(QString("FE_CAN_TRANSMISSION_MODE_AUTO"));
// if (info.caps & FE_CAN_BANDWIDTH_AUTO) emit message(QString("FE_CAN_BANDWIDTH_AUTO"));
// if (info.caps & FE_CAN_GUARD_INTERVAL_AUTO) emit message(QString("FE_CAN_GUARD_INTERVAL_AUTO"));
// if (info.caps & FE_CAN_HIERARCHY_AUTO) emit message(QString("FE_CAN_HIERARCHY_AUTO"));
// if (info.caps & FE_CAN_8VSB) emit message(QString("FE_CAN_8VSB"));
// if (info.caps & FE_CAN_16VSB) emit message(QString("FE_CAN_16VSB"));
// if (info.caps & FE_HAS_EXTENDED_CAPS) emit message(QString("FE_HAS_EXTENDED_CAPS: may be ACM."));
// if (info.caps & FE_CAN_2G_MODULATION) emit message(QString("FE_CAN_2G_MODULATION: DVB-S2."));
// if (info.caps & FE_NEEDS_BENDING) emit message(QString("FE_NEEDS_BENDING: ")); // not used
// if (info.caps & FE_CAN_RECOVER) emit message(QString("FE_CAN_RECOVER: frontend can recover from a cable unplug automatically."));
// if (info.caps & FE_CAN_MUTE_TS) emit message(QString("FE_CAN_MUTE_TS: frontend can stop spurious TS data output."));
// }

emit message(QString(tr("Frontend parameters:")));

struct dvb_frontend_parameters frp;
if (ioctl(_front, FE_GET_FRONTEND, &frp) >= 0)
{
emit message(QString("Frequency: %1 kHz.").arg(frp.frequency));

// if (frp.inversion == INVERSION_OFF) emit message(QString("Spectral inversion off."));
// if (frp.inversion == INVERSION_ON) emit message(QString("Spectral inversion on."));
// if (frp.inversion == INVERSION_AUTO) emit message(QString("Spectral inversion auto."));

emit message(QString("Symbol rate: %1 sym/sec.").arg(frp.u.qpsk.symbol_rate));
// if (frp.u.qpsk.fec_inner & FEC_NONE) emit message(QString("Code rate: %1.").arg("FEC_NONE"));
// if (frp.u.qpsk.fec_inner & FEC_1_2) emit message(QString("Code rate: %1.").arg("FEC_1_2"));
// if (frp.u.qpsk.fec_inner & FEC_2_3) emit message(QString("Code rate: %1.").arg("FEC_2_3"));
// if (frp.u.qpsk.fec_inner & FEC_3_4) emit message(QString("Code rate: %1.").arg("FEC_3_4"));
// if (frp.u.qpsk.fec_inner & FEC_4_5) emit message(QString("Code rate: %1.").arg("FEC_4_5"));
// if (frp.u.qpsk.fec_inner & FEC_5_6) emit message(QString("Code rate: %1.").arg("FEC_5_6"));
// if (frp.u.qpsk.fec_inner & FEC_6_7) emit message(QString("Code rate: %1.").arg("FEC_6_7"));
// if (frp.u.qpsk.fec_inner & FEC_7_8) emit message(QString("Code rate: %1.").arg("FEC_7_8"));
// if (frp.u.qpsk.fec_inner & FEC_8_9) emit message(QString("Code rate: %1.").arg("FEC_8_9"));
// if (frp.u.qpsk.fec_inner & FEC_AUTO) emit message(QString("Code rate: %1.").arg("FEC_AUTO"));
// if (frp.u.qpsk.fec_inner & FEC_3_5) emit message(QString("Code rate: %1.").arg("FEC_3_5"));
// if (frp.u.qpsk.fec_inner & FEC_9_10) emit message(QString("Code rate: %1.").arg("FEC_9_10"));
}

emit message(QString(""));
return true;
}

bool ADVBSDevice::tuneFrontend()
{
if ((_front = open(FRONT,O_RDWR)) < 0) {
emit message(QString(tr("Can't open frontend.")));
return false;
}

dvb_frontend_parameters frp;
frp.frequency = _freq;
frp.inversion = INVERSION_AUTO;
frp.u.qpsk.symbol_rate = _srate;
frp.u.qpsk.fec_inner = FEC_AUTO;
int attempt = 3;

do
{
emit message(QString(tr("Frontend is tunning...")));

if (ioctl(_front, FE_SET_FRONTEND, &frp) < 0) {
emit message(QString(tr("Can't set frontend.")));
return false;
}

if (isFrontendTuned()) break;

emit message (QString(tr("%1. Restart after ~3 seconds.")).arg(attempt));
sleep(3);
}
while (--attempt);

if (!attempt) {
emit message(QString(tr("Failed. Is there signal?")));
return false;
}

// pollfd pfd;
// pfd.fd = _front;
// pfd.events = POLLIN;
// dvb_frontend_event event;

// if (poll(&pfd,1,3000))
// {
// if (pfd.revents & POLLIN)
// {
// emit message(QString(tr("Getting frontend event...")));

// if ( ioctl(_front, FE_GET_EVENT, &event) == -EOVERFLOW) {
// emit message(QString(tr("Failed: overflow.")));
// return false;
// }
// }
// }

return true;
}

bool ADVBSDevice::tuneDemux()
{
if ((_demux = open(DMX, O_RDWR|O_NONBLOCK)) < 0)
{
emit message(QString(tr("Can't open demux.")));
return false;
}

dmx_pes_filter_params pesFilter;

pesFilter.pid = DMX_ALL_PIDS;
pesFilter.input = DMX_IN_FRONTEND;
pesFilter.output = DMX_OUT_TS_TAP;
pesFilter.pes_type = DMX_PES_OTHER;
pesFilter.flags = DMX_IMMEDIATE_START;

if (ioctl(_demux, DMX_SET_PES_FILTER, &pesFilter) < 0)
{
emit message(QString(tr("Can't set pes to demux.")));
return false;
}

return true;
}

bool ADVBSDevice::tuneDvr()
{
if ((_dvr = open(DVR, O_RDONLY|O_NONBLOCK)) < 0)
{
emit message(QString(tr("Can't open DVR.")));
return false;
}

return true;
}


Отмечу, что данное решение подходит для любых, поддерживаемых www.linuxtv.org устройств (DVB-S\T\C\H).
Если что-то будет не понятно по исходникам или содержанию статью, с радостью отвечу на все вопросы.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.