linux DVB 驱动分析


linux平台的数字电视驱动被称作linux DVB 驱动

其框架称为DVB core

在此做简单介绍



Demux 简介



Demux  是一个硬件模块,的主要作用是处理从frontend过来的信号,从中分离出各种表,然后根据表中的信息分离出每个频道的音频信号和视频信号,最后将音频和视频信号送到decoder解码。


linux DVB 驱动分析_第1张图片

linux DVB 驱动分析_第2张图片


数字电视节目音频视频的ES流需要打包成PES(packetized elementary stream )包,一个PES包必须切为N个TS包来传送。每个TS  长188字节。

编码器为每一路的基本业务的传送包赋予一个唯一的包标志符PID ,
同时将这些PID的赋值信息写入到节目映射表(PMT)的控制信息表中。每一路节目一个PMT表,记录的是该路节目的视频流的PID,音频流的PID。

各个PMT的PID统一记录在PAT表中。PAT的PID 为0。

TS流解析流程

TS流中查找PID为0的TS包,即PAT表
根据表获取不同频道的PMT表所使用的PID
根据这些PID获取不同频道的PMT
根据PMT表获取频道中的音视频PID
根据音视频PID 获取音视频TS包
获取到TS包之后,TS包->PES包->ES包->解码器。




linux DVB 驱动设备文件模型


linux DVB 驱动分析_第3张图片






linux DVB 驱动分析_第4张图片

DTV 播放,使用demux0
PVR Record , 使用demux1   DVR1
PVR Play, 使用demux0, DVR0


demux节点

/dev/dvb/adapter0/demux0
/dev/dvb/adapter0/demux1


1,   open: 可以多次打开。每次打开将会分配一个新的 filter  handle。
2,   read : 读取sections,即PAT,PMT表
3,ioctl:  DMX_SET_PES_FILTER  
设置pes  过滤,即只读取某pid的TS码流。
设置TS流出口


typedef enum
{
DMX_OUT_DECODER,
DMX_OUT_TAP,
DMX_OUT_TS_TAP
} dmx_output_t;

DMX_OUT_DECODER:将流输出到解码器上
DMX_OUT_TAP:将流输出到demux设备上   
  /dev/dvb/adapter0/demux0
  DMX_OUT_TS_TAP:将流输出到逻辑 DVR设备上   /dev/dvb/adapter0/dvr0
  

  

DVR 节点

dev/dvb/adapter0/dvr0

dev/dvb/adapter0/dvr1

是一个逻辑设备。

从该设备读取,将得到一个TS流。
该流就是在demux设备上设置了PES  PID过滤、设置了DMX_OUT_TS_TAP标志之后,从demux 过滤出来的TS。

index节点
dev/dvb/adapter0/dvr_idx1
dev/dvb/adapter0/dvr_idx0


用来读写index流。
typedef struct
{
	driver_picture_type_e	picture_type;
	unsigned long long		packet_count;
	unsigned int		timecode;
	unsigned long long		pts;
}pvr_index_rawdata_s;

Index是解码器生成的,并非TS流中自带原始数据。
每个单元长32字节,主要描述了某一帧数据的I\B\P类型,在TS流中的位置、PTS时间戳。


PVR录制典型例程



1,锁住tuner频率  (另文讨论,不做赘述)
2,打开dvr设备/index设备
dvb_dvr_open
dvb_dvr_idx_open
3,demux设置:
设置PES过滤所需PID,以及TS流输出方向
4,    设置trustzone加解密
5,进入循环,从dvr读,向目标文件写
While()
{
Read dvr
Read index
Write file
}


采用如下代码打开DVR设备结点

	flags = O_RDONLY | O_NONBLOCK;
	sprintf(filename, "/dev/dvb/adapter%i/dvr%i", adapter, dvr);
	fd = open(filename, flags);


采用如下代码打开index设备结点

	int flags = O_RDONLY | O_NONBLOCK;
	sprintf(filename, "/dev/dvb/adapter%i/dvr_idx%i", adapter, dvr_idx);
	fd = open(filename, flags);

采用如下代码设置index设备结点
	dvr_index_config_t  index_config; 	
	index_config.video_format = DVR_VIDEO_FAMAT_MPEG;  //根据视频流的格式设置
	index_config.interval =  0;  //通常情况下设置0
	ioctl(idxFd, DVR_IDX_SET_CONFIG, &index_config ) ;	

采用如下代码打开demux设备结点
asprintf(&demux_name, "/dev/dvb/adapter%i/demux%i", adapter, demux );
  int fd_demux = open( demux_name, O_RDWR | O_NONBLOCK );
 
  采用如下代码设置demux设备结点

对于每个需要得到的pid, 下面代码均需要调用一次。
有几个pid就调用几次,同时要将pesfilter.pes_type 字段设置成pid对应的类型。
例如一个电视节目有audio,video,pcr, 三个pid数据流,则下面代码就应该被调用三次,同时
pesfilter.pes_type分别设置成DMX_PES_AUDIO3, DMX_PES_VIDEO3, DMX_PES_PCR3

	struct dmx_pes_filter_params pesfilter;
	pesfilter.pid = pid;
	pesfilter.input = DMX_IN_FRONTEND;    //表示数据来自于前段电缆信号线
	pesfilter.output = DVB_DMX_OUT_DVR; //表示数据输出到DVR设备 
	pesfilter.pes_type = DMX_PES_AUDIO3;  //表示这一路pid数据是音频数据
	pesfilter.flags = DMX_IMMEDIATE_START; //常规设置,不需要改变
	ioctl(dmxfd, DMX_SET_PES_FILTER, &pesfilter)

  
  
  经过前面几步,初始化设置就完成了,数据就会上来,这里就可以进行循环读写了
这里一共要读两种数据,
1,TS流数据,这个就是音视频文件的数据,直接存入文件系统即可。
2,index数据,此数据主要描述音视频的帧类型和时间码 ,经过二次封装,形成GOP信息,存入文件系统即可。


While()
{
Read index data from index_dvr
Analysis index data, produce GOP information
Save the GOP information to GOP file
Read TS data from dvr
Write TS data to DVR
}


PVR播放典型例程



1,打开dvr设备
dev/dvb/adapter0/dvr0
dvb_dvr_open

2,demux设置:
dev/dvb/adapter0/demux0
设置PES过滤所需PID,以及TS流输出方向


3,设置trustzone解密 


4,进入循环,从文件读,向DVR写
While()
{
Read file
Write DVR
}


经过前面几步,初始化设置就完成了,这里就可以进行循环读写,将数据送入DVR
这里一共要处理两种数据,
1,读GOP信息,从中得到音视频帧的位置,时间码等信息。
2,根据得到的音视频帧位置,从TS文件中读出音视频帧,送入DVR。


While()
{
Read GOP file
Analysis GOP data,
Get the correct location of TS file
Read TS data from the correct location of TS file
Write TS data to DVR
}





PVR几个特殊场景

1,节目具有多路音频的场景


例如有两路音频,audio_1 ,audio_2
设置demux  pid filter的时候,要如下设置:
设置audio_1  的 pesfilter.pes_type = DMX_PES_AUDIO3;  
设置audio_2  的 pesfilter.pes_type = DMX_PES_OTHER;

如果还有更多路音频,例如三路以上,只需要把多出来的音频均设置成 DMX_PES_OTHER即可。


2,Audio_only场景
audio——only是只录制音频,不录制视频的场景,
此时设置index设备结点属性的时候,视频格式做如下设置:
index_config.video_format = DVR_VIDEO_FAMAT_NONE;




DVB 核心代码


linux DVB 驱动分析_第5张图片

Kernal/drivers/media/dvb-core
Linux自身定义的DVB框架性代码。
主要实现设备注册,设备读写,回调函数,内存管理等框架性代码
从linux框架的角度规定了对设备文件读写控制,不关心上层的具体业务,只处理设备文件相关操作。


Kernal/drivers/media/platform/sdp/sdp_demux
Dvb功能实现的具体代码。与设备硬件强相关。
是dvb-core代码中各种挂接函数的具体实现。
负责操作管理硬件。
响应dvb-core传递下来的各种上层业务,通过对硬件的操作实际完成各种具体操作。


Dvb-core 代码介绍 


Dvbdev.c
定义了DVB各设备的注册函数。
便于实际初始化函数调用。
Dmxdev.c
定义了dvb中demux、dvr、index等设备文件的操作函数框架。
例如open、ioctl、read等函数的大致形式就定义在此。其中含多函数都采用了函数指针的形式,指向具体的函数实现。
Dvb_demux.c
对demux设备文件使用到的主要结构体进行初始化。对demux的feed、filter等功能定义了框架代码。
Dvb_ringbuffer.c
定义了DVB-core所使用的环形内存读写管理方式

Demux 驱动运行流程


demux的数据输出为PVR寄存器

PVR硬件模块解封装TS流,输出ES流到解码器MFC


同时demux模块还能将TS流用DMA方式输出到到内存dvb_bufferring, 便于TS流读操作,录制TS 流

PVR还能接收dvb_bufferring用DMA方式送过来的数据流,解封TS流,输出ES流到解码器


dvb_bufferring的驱动运转是靠中断驱动的。


如果此时是在写PVR,

PVR 模块中的ES流输出给解码器后,PVR的中断会告知现在PVR中有空间可用,DMA开始写入

DMA完毕的中断发生后,触发回调函数,从dvb_bufferring 里面更新一下环,

同时通知用户态现在有空间可以写入了,

DMA的目的地址是PVR模块地址,DMA源地址是更新完毕的dvb_bufferring里面的数据区



如果此时是在读PVR,

demux模块中的TS包数量达到阈值后,产生中断,开始DMA到dvb_bufferring

DMA完毕后,DMA中断产生,从dvb_bufferring 里面更新一下环,

DMA的目的地址是dvb_bufferring里面的数据区,DMA源地址是demux寄存器



ES流buffer只有一块,PVR往里面DMA。



MFC解码足够快,1毫秒
MFC解码完后,有个PCR硬件模块,接收TS流里的时间同步信息。根据这个同步信息不断对自己调整

MFC在DTV模式下,会告诉PCR模块,这一帧的PTS和解码完比的地址。

PCR对时间做一下调整,就会通知显示模块显示


这里MFC用了多buffer技术,防止屏幕撕裂
DTV解码时,会告诉MFC现在是DTV模式,不是MM模式





  
  

你可能感兴趣的:(linux,驱动开发)