ts打包代码详解 (ffmpeg)

论坛 期权论坛 脚本     
匿名网站用户   2020-12-19 17:23   11   0

FFmpeg代码里面有ts打包和解包的代码,这里简单介绍下怎么使用吧。

先来看下FFmpeg目录结构:
libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;
libavcodec:用于各种类型声音/图像编解码;
libavutil:包含一些公共的工具函数;
libswscale:用于视频场景比例缩放、色彩映射转换;
libpostproc:用于后期效果处理;
ffmpeg:该项目提供的一个工具,可用于格式转换、解码或电视卡即时编码等;
ffsever:一个HTTP 多媒体即时广播串流服务器;
ffplay:是一个简单的播放器,使用ffmpeg库解析和解码,通过SDL显示;


libavformat目录下 mpegtsenc.c,mpegts.c 分别是ts打包和解包的代码:

下面介绍下mpegtsenc.c一些重要函数(原理请看 iso 13818-1):



1) PSI业务数据

mpegts_write_pat(AVFormatContext *s);
mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)

mpegts_write_sdt(AVFormatContext *s)//节目描述表

pat,pmt这两个表是ts打包最重要的表,这两个表说白了就是多路复用的一个索引,解码器需要更具PAT知道有哪些节目(可以理解为电视节目),根据PMT知道每个节目里面有哪些es流(每个电视节目都有音频和视频),这两个函数一般是不需要改动的;

pat,pmt的信息并不是只是开始打包的时候出现,看mpegts_write_pes代码会发现着两个表是根据retransmit_si_info计算出来的。

看下mpegts_write_pmt部分代码:

  1. //nb_streams 对应es流,如果一路音频一路视频,nb_streams=2
  2. for(i = 0; i < s->nb_streams; i++) {
  3. AVStream *st = s->streams[i];
  4. MpegTSWriteStream *ts_st = st->priv_data;
  5. switch(st->codec->codec_id) {
  6. case CODEC_ID_MPEG1VIDEO:
  7. case CODEC_ID_MPEG2VIDEO:
  8. stream_type = STREAM_TYPE_VIDEO_MPEG2;
  9. break;
  10. case CODEC_ID_MPEG4:
  11. stream_type = STREAM_TYPE_VIDEO_MPEG4;
  12. break;
  13. case CODEC_ID_H264:
  14. stream_type = STREAM_TYPE_VIDEO_H264;
  15. break;
  16. case CODEC_ID_MP2:
  17. case CODEC_ID_MP3:
  18. stream_type = STREAM_TYPE_AUDIO_MPEG1;
  19. break;
  20. case CODEC_ID_AAC:
  21. stream_type = STREAM_TYPE_AUDIO_AAC;
  22. break;
  23. case CODEC_ID_AC3:
  24. stream_type = STREAM_TYPE_AUDIO_AC3;
  25. break;
  26. default:
  27. stream_type = STREAM_TYPE_PRIVATE_DATA;
  28. break;
  29. }
  30. *q++ = stream_type;
  31. put16(&q, 0xe000 | ts_st->pid);

复制代码

从上面看出ts打包支持MPEG1,MPEG2,MPEG4,h264视频以及PCM,mp2,mp3,AAC,AC3音频,音频方面标准不支持G711等G开头的音频,当然如果自己开发客户端的话是可以自定义的。

2)mpegts_write_header(AVFormatContext *s)


初始化AVFormatContext参数,在正式封装开始加入PAT,PMT,SDT一些信息。代码中有基本的注释;

3)mpegts_write_pes(AVFormatContext *s, AVStream *st, const uint8_t *payload, int payload_size, int64_t pts, int64_t dts)


这个函数就是TS打包的主函数了,这个函数主要功能就是把一帧数据拆分成188字节(感觉效率低了点),并加入PTS,DTS同步信息,这个函数封装的对象是一帧视频或者音频数据,payload,payload_size分别是数据和大小。

PTS,DTS就是音视频同步时间戳,时间戳其实就是一次采样的颗粒(简单理解就是数据),以视频来举例,视频同步时钟90K hz(27M/300),如果帧率是25fps的话,一帧数据采样时间40ms,那么时间戳就是90K x 40ms = 3600(估算值)。
staticvoidmpegts_write_pes(AVFormatContext*s,AVStream*st,
constuint8_t*payload,intpayload_size,
int64_tpts,int64_tdts,
intkey)
{

........
while(payload_size>0){
retransmit_si_info(s,force_pat);
//评估是否需要插入PAT、PMT
force_pat
=0;

write_pcr
=0;
if(ts_st->pid==ts_st->service->pcr_pid){//评估pes中是否需要插入PCR
if(ts->mux_rate>1||is_start)//VBRpcrperiodisbasedonframes
ts_st
->service->pcr_packet_count++;
if(ts_st->service->pcr_packet_count>=
ts_st
->service->pcr_packet_period){
ts_st
->service->pcr_packet_count=0;
write_pcr
=1;
}
}

if(ts->mux_rate>1&&dts!=AV_NOPTS_VALUE&&
(dts
-get_pcr(ts,s->pb)/300)>delay){//CBR考虑(插入pcr_only或null_packet)
/*pcrinsertgetspriorityovernullpacketinsert*/
if(write_pcr)
mpegts_insert_pcr_only(s,st);
else
mpegts_insert_null_packet(s);
continue;
/*recalculatewrite_pcrandpossiblyretransmitsi_info*/
}

/*preparepacketheader*/
q
=buf;
*q++=0x47;
val
=(ts_st->pid>>8);
if(is_start)
val|
=0x40;//payload_unit_start_indicator
*q++=val;
*q++=ts_st->pid;
ts_st
->cc=(ts_st->cc+1)&0xf;//continuity_counter
*q++=0x10|ts_st->cc;//payloadindicator+CC
/*********写入adaptation_field(如果需要)*********/
if(key&&is_start&&pts!=AV_NOPTS_VALUE){
if(ts_st->pid==ts_st->service->pcr_pid)
write_pcr
=1;
set_af_flag(buf,0x40);
//setRandomAccessforkeyframes
q
=get_ts_payload_start(buf);
}
if(write_pcr){
set_af_flag(buf,0x10);
//PCR_flag
q
=get_ts_payload_start(buf);
//add11,pcrreferencesthelastbyteofprogramclockreferencebase
if(ts->mux_rate>1)
pcr
=get_pcr(ts,s->pb);
else
pcr
=(dts-delay)*300;
if(dts!=AV_NOPTS_VALUE&&dts<pcr/300)
av_log(s,AV_LOG_WARNING,
"dts<pcr,TSisinvalid\n");
extend_af(buf,write_pcr_bits(q,pcr));
//写入pcr
q
=get_ts_payload_start(buf);
}
/*********写入payload*********/
if(is_start){
intpes_extension=0;
intpes_header_stuffing_bytes=0;
/*writePESheader*/
*q++=0x00;
*q++=0x00;
*q++=0x01;
is_dvb_subtitle
=0;
is_dvb_teletext
=0;
/*写入stream_id*/
if(st->codec->codec_type==AVMEDIA_TYPE_VIDEO){
if(st->codec->codec_id==AV_CODEC_ID_DIRAC){
*q++=0xfd;
}
else
*q++=0xe0;//videostream
}
elseif(st->codec->codec_type==AVMEDIA_TYPE_AUDIO&&
(st
->codec->codec_id==AV_CODEC_ID_MP2||
st
->codec->codec_id==AV_CODEC_ID_MP3||
st
->codec->codec_id==AV_CODEC_ID_AAC)){
*q++=0xc0;
}
elseif(st->codec->codec_type==AVMEDIA_TYPE_AUDIO&&
st
->codec->codec_id==AV_CODEC_ID_AC3&&
ts
->m2ts_mode){
*q++=0xfd;
}
else{
*q++=0xbd;
if(st->codec->codec_type==AVMEDIA_TYPE_SUBTITLE){
if(st->codec->codec_id==AV_CODEC_ID_DVB_SUBTITLE){
is_dvb_subtitle
=1;
}
elseif(st->codec->codec_id==AV_CODEC_ID_DVB_TELETEXT){
is_dvb_teletext
=1;
}
}
}
header_len
=0;
flags
=0;
/*处理PTS_DTS_flags*/
if(pts!=AV_NOPTS_VALUE){
header_len
+=5;
flags|
=0x80;
}
if(dts!=AV_NOPTS_VALUE&&pts!=AV_NOPTS_VALUE&&dts!=pts){
header_len
+=5;
flags|
=0x40;
}
........
len=payload_size+header_len+3;
/*3extrabytesshouldbeaddedtoDVBsubtitlepayload:0x200x00atthebeginningandtrailing0xff*/
if(is_dvb_subtitle){
len+=3;
payload_size
++;
}
if(len>0xffff)
len=0;
if(ts->omit_video_pes_length&&st->codec->codec_type==AVMEDIA_TYPE_VIDEO){
len=0;
}
*q++=len>>8;//PES_packet_length
*q++=len;
val
=0x80;//'10'
/*dataalignmentindicatorisrequiredforsubtitleanddatastreams*/
if(st->codec->codec_type==AVMEDIA_TYPE_SUBTITLE||st->codec->codec_type==AVMEDIA_TYPE_DATA)
val|
=0x04;
*q++=val;
*q++=flags;
*q++=header_len;//PES_header_data_length
if(pts!=AV_NOPTS_VALUE){
write_pts(q,flags
>>6,pts);//写入pts
q
+=5;
}
if(dts!=AV_NOPTS_VALUE&&pts!=AV_NOPTS_VALUE&&dts!=pts){
write_pts(q,
1,dts);//写入dts
q
+=5;
}
........
/*处理完header,下面开始处理数据*/
/*headersize*/
header_len
=q-buf;
/*datalen*/
len=TS_PACKET_SIZE-header_len;
if(len>payload_size)//pes包太大,一个ts包发不完,就得拆包。
len=payload_size;
stuffing_len
=TS_PACKET_SIZE-header_len-len;//相反,如果太小,就加入填充。
if(stuffing_len>0){
/*addstuffingwithAFC*///
if(buf[3]&0x20){
/*stuffingalreadypresent:increaseitssize*/
afc_len
=buf[4]+1;
memmove(buf
+4+afc_len+stuffing_len,
buf
+4+afc_len,
header_len
-(4+afc_len));
buf[
4]+=stuffing_len;
memset(buf
+4+afc_len,0xff,stuffing_len);
}
else{
/*addstuffing*/
memmove(buf
+4+stuffing_len,buf+4,header_len-4);
buf[
3]|=0x20;
buf[
4]=stuffing_len-1;
if(stuffing_len>=2){
buf[
5]=0x00;
memset(buf
+6,0xff,stuffing_len-2);
}
}
}

if(is_dvb_subtitle&&payload_size==len){
memcpy(buf
+TS_PACKET_SIZE-len,payload,len-1);
buf[TS_PACKET_SIZE
-1]=0xff;/*end_of_PES_data_field_marker:an8-bitfieldwithfixedcontents0xffforDVBsubtitle*/
}
else{
memcpy(buf
+TS_PACKET_SIZE-len,payload,len);//写入payload数据
}

payload
+=len;
payload_size
-=len;
mpegts_prefix_m2ts_header(s);
avio_write(s
->pb,buf,TS_PACKET_SIZE);//写入avio(缓存到avio_buf中)
}
avio_flush(s
->pb);//把avio_buf的数据写入后端(file/udp等)
ts_st
->prev_payload_key=key;
}


4)mpegts_write_packet(AVFormatContext *s,AVPacket *pkt)

这个函数功能比较简单,就是把一帧数据拆分成几个块来封装成pes,因为pes头信息的长度只有两个字节长度(当时可能面向标清),高清的I帧数据肯定一次包不完的。

staticintmpegts_write_packet_internal(AVFormatContext*s,AVPacket*pkt)
{
........
if(st->codec->codec_id==AV_CODEC_ID_H264){
constuint8_t*p=buf,*buf_end=p+size;
uint32_tstate
=-1;
intret=ff_check_h264_startcode(s,st,pkt);
if(ret<0)
returnret;

do{
p
=avpriv_find_start_code(p,buf_end,&state);
av_dlog(s,
"nal%d\n",state&0x1f);
}
while(p<buf_end&&(state&0x1f)!=9&&
(state
&0x1f)!=5&&(state&0x1f)!=1);//nal_unit_type

if((state&0x1f)!=9){//AUDNAL
data
=av_malloc(pkt->size+6);
if(!data)
returnAVERROR(ENOMEM);
memcpy(data
+6,pkt->data,pkt->size);
AV_WB32(data,0x00000001);
data[
4]=0x09;
data[
5]=0xf0;//anyslicetype(primary_pic_type=0xe)+rbspstoponebit
buf
=data;
size
=pkt->size+6;
}

if(pkt->dts!=AV_NOPTS_VALUE){
inti;
for(i=0;i<s->nb_streams;i++){
AVStream
*st2=s->streams[i];
MpegTSWriteStream
*ts_st2=st2->priv_data;
if(ts_st2->payload_size
&&(ts_st2->payload_dts==AV_NOPTS_VALUE||dts-ts_st2->payload_dts>delay/2)){
mpegts_write_pes(s,st2,ts_st2
->payload,ts_st2->payload_size,
ts_st2
->payload_pts,ts_st2->payload_dts,
ts_st2
->payload_flags&AV_PKT_FLAG_KEY);//优先处理其它码流fifo数据(多码流同步考虑)
ts_st2
->payload_size=0;
}
}
}

if(ts_st->payload_size&&ts_st->payload_size+size>ts->pes_payload_size){
mpegts_write_pes(s,st,ts_st
->payload,ts_st->payload_size,
ts_st
->payload_pts,ts_st->payload_dts,
ts_st
->payload_flags&AV_PKT_FLAG_KEY);//处理当前码流fifo数据
ts_st
->payload_size=0;
}

if(st->codec->codec_type!=AVMEDIA_TYPE_AUDIO||size>ts->pes_payload_size){
av_assert0(!ts_st
->payload_size);
//forvideoandsubtitle,writeasinglepespacket
mpegts_write_pes(s,st,buf,size,pts,dts,pkt
->flags&AV_PKT_FLAG_KEY);//满一个pes帧,直接写入。
av_free(data);
return
0;
}

if(!ts_st->payload_size){//初始化pts、dts
ts_st
->payload_pts=pts;
ts_st
->payload_dts=dts;
ts_st
->payload_flags=pkt->flags;
}

memcpy(ts_st
->payload+ts_st->payload_size,buf,size);//不满一个pes帧,先缓存到fifo。
ts_st
->payload_size+=size;

av_free(data);

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP