ts打包代码详解 (ffmpeg)

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(估算值)。
static void mpegts_write_pes(AVFormatContext  * s, AVStream  *st,
                              const  uint8_t  * payload,  int payload_size,
                             int64_t pts, int64_t dts, 
int key)
{
    
........
    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)  //  VBR pcr period  is  based  on  frames
                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)
            
/*  pcr insert gets priority over  null  packet insert  */
            
if  (write_pcr)
                mpegts_insert_pcr_only(s, st);
            
else
                mpegts_insert_null_packet(s);
            continue; 
/*  recalculate write_pcr  and  possibly retransmit si_info  */
        }

        
/*  prepare packet header  */
        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;  //  payload indicator  +  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); 
// set  Random Access  for  key frames
            q 
=  get_ts_payload_start(buf);
        }
        
if  (write_pcr) {
            set_af_flag(buf, 0x10); 
// PCR_flag
            q 
=  get_ts_payload_start(buf);
            
//  add  11 , pcr references the last  byte  of program clock reference base
            
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, TS is invalid\n " );
            extend_af(buf, write_pcr_bits(q, pcr)); 
// 写入pcr
            q 
=  get_ts_payload_start(buf);
        }
        
/********* 写入payload *********/
        
if  (is_start) {
            
int  pes_extension  =   0 ;
            
int  pes_header_stuffing_bytes  =   0 ;
            
/*  write PES header  */
            
* 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;     // video stream
            } 
else   if  (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;
            } 
else   if  (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 ;
                    } 
else   if  (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 ;
            
/*   3  extra bytes should be added  to  DVB subtitle payload: 0x20 0x00 at the beginning  and  trailing 0xff  */
            
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'
             /*  data alignment indicator  is  required  for  subtitle  and  data streams  */
            
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,下面开始处理数据  */
        
/*  header size  */
        header_len 
=  q  -  buf;
        
/*  data  len   */
        
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 ) {
            
/*  add stuffing  with  AFC  */       //
            
if  (buf[ 3 &  0x20) {
                
/*  stuffing already present: increase its size  */
                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  {
                
/*  add stuffing  */
                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: an  8 - bit field  with  fixed contents 0xff  for  DVB subtitle  */
        } 
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帧数据肯定一次包不完的。

static  int  mpegts_write_packet_internal(AVFormatContext  * s, AVPacket  *pkt)
{
........
     if  (st -> codec -> codec_id  ==  AV_CODEC_ID_H264) {
        
const  uint8_t  * =  buf,  * buf_end  =  p + size;
        uint32_t state 
=   - 1 ;
        
int  ret  =  ff_check_h264_startcode(s, st, pkt);
        
if  (ret  <   0 )
            return ret;

        
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 ) {  //  AUD NAL
            data 
=  av_malloc(pkt -> size + 6 );
            
if  (!data)
                return AVERROR(ENOMEM);
            memcpy(data
+ 6 , pkt -> data, pkt -> size);
            AV_WB32(data, 0x00000001);
            data[
4 =  0x09;
            data[
5 =  0xf0;  //  any slice type (primary_pic_type  =  0xe)  +  rbsp stop one bit
            buf  
=  data;
            size 
=  pkt -> size + 6 ;
        }

    
if  (pkt -> dts ! =  AV_NOPTS_VALUE) {
        
int  i;
        
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);
        
//   for  video  and  subtitle, write a  single  pes packet
        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);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值