自己这几天完成了ffmpeg发送与接收处理的模块,写下一篇说明性的文档,供大家借阅和批评。
网络视频流的解码
开发环境:CentOS6.1,ffmpeg0.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字段所指的数据,该数据即是视频数据或音频数据,若判断为视频数据再进行下一步处理,而Packet的size字段标识了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
{
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流。TS与PS都是一种复合流,其包含了视频流、音频流等其他,针对这种流可通过格式探测函数,探测出对其进行解码需要的解码器类型,然后用该解码器进行解码即可。TS与PS不同之处在于,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.未采用多线程机制,应该再创建一个线程,主线程用来接收数据,另一线程则专门负责解码。本接收端就采用主线程一个进行所有的处理,这在解码时若执行的时间较长,会造成接收端的瓶颈,此时发过来的视频数据将不被接收。