ffmpeg网络流解码

来源:互联网 发布:淘宝代写征文靠谱 编辑:程序博客网 时间:2024/05/19 17:07

自己这几天完成了ffmpeg发送与接收处理的模块,写下一篇说明性的文档,供大家借阅和批评。 

网络视频流的解码

开发环境:CentOS6.1ffmpeg0.10.3

1.发送端

1.1主要功能描述

在发送端,可以自定义要发送视频的帧数,或者整个视频文件;发送时的速率大小;对于视频的编码格式,只要ffmpeg支持的,且接收端安装了相应的解码器的,都可以进行发送,接收端收到一视频帧后便进行解码,再保存成ppm格式图片。

1.2关键技术概要

发送端在发送实际视频帧之前,首先对于接收端解码视频需要的关键信息给予了发送,接收端解码需要的关键信息有:相应视频的解码器ID,视频帧的长、宽度。因为接收端在不知道发送过来的视频帧的相关信息之前,无法正常解码,要获得这些信息可以有两种方法:一是发送端与接收端对于视频的编码格式、视频帧的长、宽协定好,双方只能对特定格式的视频进行操作;二是双方不协定,通过发送端把关键信息发送给接收端,接收端接收后,用该信息以正确初始化解码前的相关处理,以此达到双方的协同。很明显,第二种方法灵活多变,本设计采用第二种。关键信息结构体如下所示:

structCtx_decoder

{

    uint16_t decoder_ID;//解码器ID,在ffmpeg中唯一标识某一解码器

    uint16_t width; //视频帧的长,单位px

    uint16_t height; //视频帧的宽,单位px

}My_Ctxdecoder;

实际发送数据时,发送端针对视频帧超过1350B的情况,进行了拆帧,加头,最后分组分送;视频帧小于等于1350B的就直接加头发送。传输层协议采用UDP,以满足视频传输的实时性为主要目的,忽略少量的丢包现象。分组头部信息为:

structVideo_Packet   //分组头部结构体

{

intpacket_cnt;      //该帧拆分为分组的数量       

intvideo_total_size; //该帧的总大小

intvideo_size;      //该帧拆分为分组后,分组携带的视频数据量

intpacket_idx;      //拆分后的分组的序列号,在同一帧内有效

intFrame_seq;       //该帧的序列号

intint_flag;        //所有分组的数据量是否都为1350B

intmore_flag;        //是否还有分组

uint8_tdata[VIDEO_SIZE]; //实际视频数据

};

该结构体关键字段有:packet_cnt,它可以控制一视频帧的发送完成,在接收端也可用来判断一视频帧是否接收完毕(假如未丢包),若一帧接收完毕,则再进行解码处理;packet_idx主要用来在接收端乱序重组,它唯一确定了该分组在所属视频帧的相对位置;Frame_seq唯一标识了某一视频帧,以便接收端判断某分组所属的帧。

至于发送端发送的关键技术,主要体现在:1读取一Packet后,获取其data字段所指的数据,该数据即是视频数据或音频数据,若判断为视频数据再进行下一步处理,而Packetsize字段标识了data指向数据的大小;2拆帧处理,某一视频帧大小超过1350B则进行拆分,拆分时对于Packet里的data指针,务必转换为char*或者unsign char *或者uint8_t *类型,简而言之,转换为指向单字节的地址,这有利于地址的转移,且转移的最小单位可以为1B大小。

具体拆帧代码如下:

p_packet=(char*)(packet.data);//转换指针类型

   init_packet(&my_packet);

if(packet.size%VIDEO_SIZE==0)

{          

        packet_num=packet.size/VIDEO_SIZE;

        my_packet.int_flag=1;

    }

    else

        packet_num=packet.size/VIDEO_SIZE+1;

    my_packet.packet_cnt=packet_num;//拆分为分组的总数量

     my_packet.Frame_seq=count;  //该帧的序列号       my_packet.video_total_size=packet.size;//该分组的视频数据大小

    for(ii=0;ii<my_packet.packet_cnt;ii++)

    {

        if((ii+1==my_packet.packet_cnt)&&(my_packet.int_flag==0))            {

         my_packet.video_size=my_packet.video_total_size%VIDEO_SIZE;

         my_packet.flag_more=0; //最后一分组,后面再无属同帧的分组

        }

        else

        {

            my_packet.video_size=VIDEO_SIZE;

            my_packet.flag_more=1;//后面还有分组

         }

         if(time_n>0)

               usleep(time_n); //休眠time_n微秒

          my_packet.packet_idx=ii+1;memcpy(my_packet.data,p_packet+(my_packet.packet_idx-1)*VIDEO_SIZE,my_packet.video_size);//拆帧准备发送

         send_size=sendto(socket_client_id,&my_packet,size,0,(structsockaddr *)&server_address,addr_len);//无连接发送

      …… //判断发送是否成功及处理

}

发送时的截图如下:第一个视频帧被拆分成了3份,最后一份数据量只有363B

第二个视频帧被拆分成了2份,总数据量为1928B

 

 

2.接收端

2.1主要功能描述

接收端可以自定义接收缓冲区的大小,以方便测试缓冲区溢出时的解码后的图片质量;无需指定解码器类型,通过收到的关键信息予以初始化解码前的处理;对于接收到的视频帧,若该帧被成功解码,则会显示成功解码的标志。

2.2关键技术概要

接收端每接收一个分组后,首先判断分组所属的视频帧,根据分组所在视频帧的索引值,随后放入特定的缓存位置,待所有的分组都接收到后,或者视频帧的最后一分组已经接收(中间的分组有可能丢失),再进行视频解码,保存成PPM格式图片,最后解码完毕,重新清空接收缓存,继续接收。

目前网络上的视频流大致可分为3钟:TS流,PS流,ES流。TSPS都是一种复合流,其包含了视频流、音频流等其他,针对这种流可通过格式探测函数,探测出对其进行解码需要的解码器类型,然后用该解码器进行解码即可。TSPS不同之处在于,TS流的包结构长度固定不变,而PS流的包结构长度可变,TS流的抗干扰性强,传输误码率低,主要用在实时广播的电视节目;PS流抗干扰性弱,在不可靠信道上传输误码率高,其主要应用于固定时长的节目,如DVD电影。对于ES流,其是一种裸流,要么为视频流要么为音频流或其他,解码ES流之前必须用相应的解码器进行解码,因为这种流其不包含额外的其他信息,因此不能通过格式探测来自动获取解码需要的信息。本发送端发送的就是ES视频流。

解码文件与解码网络ES视频流之间的最大区别就是:网络ES视频流解码时需要指定合适的解码器,而解码文件可自动获取相应的解码器信息。解码网络流前的关键自定义处理函数如下:

uint8_t *Init_decoder(struct Ctx_picture*A,struct Ctx_decoder *B)

{

   int num_bytes;

   uint8_t *buf;

   avcodec_register_all();

   av_init_packet(&(A->avpkt));

   A->codec = avcodec_find_decoder(B->decoder_ID);

   if (!(A->codec))

    {

       fprintf(stderr, "codec not found\n");

       exit(1);

    }

   A->c= avcodec_alloc_context3(A->codec);

   A->c->width=B->width;

   A->c->height=B->height;

   //if((A->codec)->capabilities&CODEC_CAP_TRUNCATED)

       //(A->c)->flags|= CODEC_FLAG_TRUNCATED;

   if (avcodec_open2(A->c, A->codec,NULL) < 0)

    {

       fprintf(stderr, "could notopen codec\n");

       exit(1);

    }

   A->pFrame=avcodec_alloc_frame();

   A->pFrameRGB=avcodec_alloc_frame();

   if(!(A->pFrameRGB)||!(A->pFrame))                                                 exit(1);                                                

num_bytes=avpicture_get_size(PIX_FMT_RGB24,(A->c)->width,(A->c)->height);

    buf=(uint8_t*)av_malloc(num_bytes*sizeof(uint8_t));          return buf;

}

后面的处理和视频文件解码类似,只不过AVPacket里的data指向接收视频帧的缓冲区,size为视频帧大小。接收端解码时的屏幕显示信息如下:

got_picture_flag不为0则说明解码再保存为图片成功。

解码后的图片示例如下:


 

3.优缺点分析

3.1发送端优点

1.发送端发送视频数据时,可以自定义发送时的速率大小。

2.对视频格式没限制,只要ffmpeg支持,且相应的解码器已安装。

3.2发送端缺点

1.视频帧是否拆分的界限为1350B,没有提供自定义设置的功能。

2.未采用rtp/rtcp协议进行传输与控制。

3.3接收端优点

1.自定义接收缓冲区的大小,方便在缓冲区大小不足的情况下进行解码测试。

2.对于接收到的视频帧分组,可乱序重排,只要分组不丢失,这不影响解码。

3.假如某一分组丢失,但最后一分组不丢失,不影响最终被解码,只是图片质量稍差。

3.4接收端缺点

1.假如某一帧的最后一个分组丢失,则该整个视频帧不被解码,中间分组丢失不影响最终被解码。

2.未采用多线程机制,应该再创建一个线程,主线程用来接收数据,另一线程则专门负责解码。本接收端就采用主线程一个进行所有的处理,这在解码时若执行的时间较长,会造成接收端的瓶颈,此时发过来的视频数据将不被接收。