RTP - 视频流广播
来源:互联网 发布:mac版word的选项在哪 编辑:程序博客网 时间:2024/06/07 17:57
1.FFSERVER
FFMPEG2的DEMO,说它有名只是因为这类程序太少了。FFMPEG2是很好用,我现在还在用,但这个DEMO就有很多“炒作”的嫌疑了。好像在做着FFMPEG2库的演示而不是真的视频流服务器。后来想想,这不正是作者想要的吗,但这不是我想要的。编解码部分我会很偏向FFMPEG这个“大杂会”,其它部分我会选择其它的“强者”
2.Darwin、Helix
两个都是非常有名软件,也只能称之为软件了,因为就算Darwin有源码,这种代码规模,也不适合用于嵌入式。说回软件本身,真的很有名。它们都是很真真拿来商业化运行的软件,但我是研发人员,不是视频流服务商,对不起,Apple,对不起,Microsoft。
3.LIVE555
如果说上面两个和我都相关性为零(当然了,也是困扰了N周以后痛苦得出的结论),那LIVE555真的给了我一条出路,它是一个代码规模非常合适,又非常强大的媒体解决方案(称之为方案是因为它功能非常的丰富)。有长一段时间,我想去弄懂它的源码,不过和网上的很多人一样,最后软下来了,毕竟,去把这么多东西揉在一起,框架会弄得很复杂,因为我们要把这些完全不同的东西不断一层一层的抽像,最后抽像成一样(哲学呀)。它结构复杂是我中断分析它原来的其中一个原因,但不是主要原因。它结构的复杂程度也没胡像很多人网上说的那样严重,如果你是一个C++的热忱爱好者,你反而会迷上这段代码,当然了,对C的爱好都来说,当然是一种折磨了。暂时把我自己归类在C++爱好者范畴吧,呵呵,我很欣赏这段原码。主要原因是我不希望被某一个库绑死。LIVE555是有编解码能力,但我更希望它只做服务器的工作。
因此,最终后回来的老路上来,没有帮助,就得自己帮自己,从最基础的RFC看起。经过了N天(周)的英文,终于领会了如果在RTP承载MPEG数据包。在这个过程中很得到了一些LIVE555的帮助(通过对Ethereal捕捉的LIVE555数据包进行分析)。先把程序弄上来,原理性的以后有空再写,程序只有一个.cpp文件,在vs.net 2003下编译通过,播放的视频文件在http://www.cnitblog.com/Files/tinnal/ES流解释程序.rar 内,播放的客户端采用VLC,其下载地址为http://www.videolan.org/。选择打开网络串流,然后选择“UDP/RTP”端口,输入程序的输出端口1000,然后才运行程序,你将在VLC内看到测试的广播视频,IP不一样的话自己改改就行。其它所谓的原理性的,也就是看RFC 3350、RFC2550以及iso13818-2的一些重点地方。
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#include<string.h>
#include<winsock2.h>
#include<winsock2.h>
//#include "mem.h"
//<! Start code or other signal
#define PACK_STARTCODE (unsigned int)0x000001ba
#define SYSTEM_HEADER_STARTCODE (unsigned int)0x000001bb
#define PICTURE_START_CODE (unsigned int)0x00000100
#define GROUP_START_CODE (unsigned int)0x000000B8
#define ISO_11172_ENDCODE (unsigned int)0x000001b9
#define SEQUENCE_HEADER_CODE (unsigned int)0x000001b3
#define PACKET_BUFFER_END (unsigned int)0x00000000
#define MAX_RTP_PKT_LENGTH 1440
#define HEADER_LENGTH 16
#define DEST_IP "192.168.0.98"
#define DEST_PORT 1000
#define MPA 14 /*MPEG PAYLOAD TYPE */
#define MPV 32
typedefstruct
{
/**//* byte 0 */
unsigned char csrc_len:4; /**//* expect 0 */
unsigned char extension:1; /**//* expect 1, see RTP_OP below */
unsigned char padding:1; /**//* expect 0 */
unsigned char version:2; /**//* expect 2 */
/**//* byte 1 */
unsigned char payload:7; /**//* RTP_PAYLOAD_RTSP */
unsigned char marker:1; /**//* expect 1 */
/**//* bytes 2, 3 */
unsignedshort seq_no;
/**//* bytes 4-7 */
unsigned long timestamp;
/**//* bytes 8-11 */
unsigned long ssrc; /**//* stream number is used here. */
} RTP_FIXED_HEADER;
typedefstruct {
//byte 0
unsigned char TR_high2:2; /**//* Temporal Reference high 2 bits*/
unsigned char T:1; /**//* video specific head extension flag */
unsigned char MBZ:5; /**//* unused */
//byte1
unsigned char TR_low8:8; /**//* Temporal Reference low 8 bits*/
//byte3
unsigned char P:3; /**//* picture type; 1=I,2=P,3=B,4=D */
unsigned char E:1; /**//* set if last byte of payload is slice end code */
unsigned char B:1; /**//* set if start of payload is slice start code */
unsigned char S:1; /**//* sequence header present flag */
unsigned char N:1; /**//* N bit; used in MPEG 2 */
unsigned char AN:1; /**//* Active N bit */
//byte4
unsigned char FFC:3; /**//* forward_f_code */
unsigned char FFV:1; /**//* full_pel_forward_vector */
unsigned char BFC:3; /**//* backward_f_code */
unsigned char FBV:1; /**//* full_pel_backward_vector */
} MPEG_VID_SPECIFIC_HDR; /**//* 4 BYTES */
enum reading_status {
SLICE_AGAIN,
SLICE_BREAK,
UNKNOWN,
SLICE,
SEQUENCE_HEADER,
GROUP_START,
PICTURE
};
void validate_file();
float frame_rate(int buffer_index);
unsignedint read_picture_type(int buffer_index);
unsignedint read_FBV(int buffer_index);
unsignedint read_BFC(int buffer_index);
unsignedint read_FFV(int buffer_index);
unsignedint read_FFC(int buffer_index);
unsignedint extract_temporal_reference(int buffer_index);
unsignedint find_next_start_code(unsignedint *buffer_index);
void reset_buffer_index(void);
BOOL InitWinsock();
//这个程序主要用于RTP封装MPEG2数据的学习和测试,不作任何其它用途
//软件在VS.net 2003中编译通过,但在linux下作小量修改也应编译通过。
//通过VLC测试,VLC能正确接收和解码由本程序发送的TEST.MPV编码流。
//
//作者:冯富秋 Tinnal
//邮箱:tinnal@163.com
#include"MPEG2RTP.h"
#pragma comment(lib,"Ws2_32")
unsignedchar buf[MAX_RTP_PKT_LENGTH+ 4];//input buffer
enum reading_status state= SEQUENCE_HEADER;
unsignedint g_index_in_packet_buffer= HEADER_LENGTH;
static unsignedlong g_time_stamp= 0;
static unsignedlong g_time_stamp_current=0;
static float g_frame_rate= 0;
static unsignedint g_delay_time= 0;
static unsignedint g_timetramp_increment= 0;
FILE *mpfd;
SOCKET socket1;
RTP_FIXED_HEADER *rtp_hdr;
MPEG_VID_SPECIFIC_HDR *mpeg_hdr;
#if 0
void Send_RTP_Packet(unsignedchar *buf,int bytes)
{
int i = 0;
int count = 0;
printf("/nPacket length %d/n",bytes);
printf("RTP Header: [M]:%s [sequence number]:0x%lx [timestamp]:0x%lx/n",
rtp_hdr->marker== 1?"TRUE":"FALSE",
rtp_hdr->seq_no,
rtp_hdr->timestamp);
printf(" [TR]:%d [AN]:%d [N]:%d [Sequence Header]:%s /
/n [Begin Slice]:%s [End Slice]:%s /
/n [Pictute Type]:%d /
/n [FBV]:%d [BFC]:%d [FFV]:%d [FFC]:%d/n",
(mpeg_hdr->TR_high2<< 8 | mpeg_hdr->TR_low8),
mpeg_hdr->AN, mpeg_hdr->N, mpeg_hdr->S== 1?"TRUE":"FALSE",
mpeg_hdr->B==1?"TRUE":"FALSE", mpeg_hdr->E==1?"TRUE":"FALSE",
mpeg_hdr->P,
mpeg_hdr->FBV, mpeg_hdr->BFC, mpeg_hdr->FFV, mpeg_hdr->FFC);
while(bytes--)
{
printf("%02x",buf[count++]);
if(++i== 16)
{
i=0;
printf("/n");
}
}
printf("/n");
}
#else
Send_RTP_Packet(unsignedchar *buf,int bytes)
{
return send( socket1, (char*) buf, bytes,0 );
}
#endif
void main(int argc,char *argv[])
{
unsignedint next_start_code;
unsignedint next_start_code_index;
unsignedint sent_bytes;
unsignedshort seq_num=0;
unsignedshort stream_num= 10;
struct sockaddr_in server;
int len =sizeof(server);
#if 0
mpfd= fopen("E://tinnal//live555//vc_proj//es//Debug//test.mpv","rb");
#else
if (argc < 2)
{
printf("/nUSAGE: %s mpegfile/nExiting../n/n",argv[0]);
exit(0);
}
mpfd= fopen(argv[1],"rb");
#endif
if (mpfd == NULL )
{
printf("/nERROR: could not open input file %s/n/n",argv[1]);
exit(0);
}
rtp_hdr= (RTP_FIXED_HEADER*)&buf[0];
mpeg_hdr= (MPEG_VID_SPECIFIC_HDR*)&buf[12];
memset((void *)rtp_hdr,0,12);//zero-out the rtp fixed hdr
memset((void *)mpeg_hdr,0,4);//zero-out the video specific hdr
memset((void *)buf,0,MAX_RTP_PKT_LENGTH+ 4);
InitWinsock();
server.sin_family=AF_INET;
server.sin_port=htons(DEST_PORT); //server的监听端口
server.sin_addr.s_addr=inet_addr(DEST_IP);//server的地址
socket1=socket(AF_INET,SOCK_DGRAM,0);
connect(socket1, (const sockaddr*)&server, len) ;
//read the first packet from the mpeg file
//always read 4 extra bytes in (in case there's a startcode there)
//but dont send more than MAX_RTP_PKT_LENGTH in one packet
fread(&(buf[HEADER_LENGTH]), MAX_RTP_PKT_LENGTH-HEADER_LENGTH+4,1,mpfd);
validate_file();
do
{
/**//* initialization of the two RTP headers */
rtp_hdr->seq_no = htons(seq_num++);
rtp_hdr->payload = MPV;
rtp_hdr->version = 2;
rtp_hdr->marker = 0;
rtp_hdr->ssrc = htonl(stream_num);
mpeg_hdr->S= mpeg_hdr->E= mpeg_hdr->B= 0;
do{
next_start_code= find_next_start_code(&next_start_code_index);
if ((next_start_code>0x100)&& (next_start_code<0x1b0) )
{
//<! We reach the first slice start code in current packet buffer.
//<! Set the B flag of the mpeg special header
if(state == SEQUENCE_HEADER
|| state ==GROUP_START
|| state ==PICTURE
|| state == UNKNOWN)
{
state= SLICE;
mpeg_hdr->B= 1;
}
//<! We reach slice start code in current packet again.
//<! Set the E flag of the mpeg special header,
//<! and update the sent_bytes to the last slice data end.
else if (state== SLICE ||state == SLICE_AGAIN)
{
state= SLICE_AGAIN;
sent_bytes= next_start_code_index;
mpeg_hdr->E = 1;
}
//<! We reach slice start code(the previous slice end)
//<! for a broken slice. set the E flag.
//<! According to RFC2550, we shouldn't put another slice data to this packet,
//<! instead of sent it out.
else if (state== SLICE_BREAK)
{
state= UNKNOWN;
sent_bytes= next_start_code_index;
mpeg_hdr->E = 1;
goto Sent_Packet;
}
}
switch(next_start_code)
{
case SEQUENCE_HEADER_CODE:
//<! SEQUENCE_HEADER_CODE after SLICE_START_CODE
//<! we must sent the packet now, so that, the SEQUENCE_HEADER_CODE
//<! will appear at the start of the next packet
if(state == SLICE || state== SLICE_AGAIN)
{
state= SEQUENCE_HEADER;
sent_bytes= next_start_code_index;
//<! Accord to RFC 2550,
//<! at the end of a frame we should set RTP marker bit to 1.
rtp_hdr->marker= 1;
goto Sent_Packet;
}
state= SEQUENCE_HEADER;
g_frame_rate= frame_rate(next_start_code_index);
g_delay_time= (unsignedint)(1000.0 / g_frame_rate+0.5);//ms
g_timetramp_increment= (unsignedint)(90000.0 / g_frame_rate+0.5);//90K Hz
mpeg_hdr->S=1;
break;
case GROUP_START_CODE:
//<! GROUP_START_CODE after SLICE_START_CODE
//<! we must sent the packet now, so that, the GROUP_START_CODE
//<! will appear at the start of the next packet
if(state == SLICE || state== SLICE_AGAIN)
{
state= GROUP_START;
sent_bytes= next_start_code_index;
//<! Accord to RFC 2550,
//<! at the end of a frame we should set RTP marker bit to 1.
rtp_hdr->marker= 1;
goto Sent_Packet;
}
state= GROUP_START;
case PICTURE_START_CODE:
//<! PICTURE_START_CODE after PICTURE_START_CODE
//<! we must sent the packet now, so that, the PICTURE_START_CODE
//<! will appear at the start of the next packet
if(state == SLICE || state== SLICE_AGAIN)
{
state= PICTURE;
sent_bytes= next_start_code_index;
//<! Accord to RFC 2550,
//<! at the end of a frame we should set RTP marker bit to 1.
rtp_hdr->marker= 1;
goto Sent_Packet;
}
state= PICTURE;
mpeg_hdr->TR_high2 = (extract_temporal_reference(next_start_code_index)& 0x300 )>> 8;
mpeg_hdr->TR_low8 = extract_temporal_reference(next_start_code_index)& 0xff;
mpeg_hdr->P = read_picture_type(next_start_code_index);
//now read the motion vectors information
if( (mpeg_hdr->P==2)|| (mpeg_hdr->P==3))
{//if B- or P-type picture, need forward mv
mpeg_hdr->FFV= read_FFV(next_start_code_index);
mpeg_hdr->FFC= read_FFC(next_start_code_index);
}
if( mpeg_hdr->P==3)
{// if B-type pictue, need backward mv
mpeg_hdr->FBV= read_FBV(next_start_code_index);
mpeg_hdr->BFC= read_BFC(next_start_code_index);
}
//<! Time stamp only increate per frame.
//<! But I or P frame.
if( mpeg_hdr->P== 1 || mpeg_hdr->P== 2 ){
g_time_stamp+= g_timetramp_increment;
g_time_stamp_current= g_time_stamp;
}else{
g_time_stamp+= g_timetramp_increment;
}
break;
case PACKET_BUFFER_END:
//<! There is one more slice in the packet buffer
//<! Anyway, we only sent the integrated slice
if(state == SLICE_AGAIN) {
state= UNKNOWN;
goto Sent_Packet;
}
//<! There is one Slice in the packet buffer.
//<! But the Slice is to big, so we break the slice.
if(state == SLICE)
{
state= SLICE_BREAK;
sent_bytes= next_start_code_index;
goto Sent_Packet;
}
//<! There if a broke slice, but in current packet buffer
//<! we could not find the end of the slice.
//<! Let it in the broke state.
if(state == SLICE_BREAK )
{
state= SLICE_BREAK;
sent_bytes= next_start_code_index;
goto Sent_Packet;
}
break;
}
}while(next_start_code!= PACKET_BUFFER_END);
Sent_Packet:
rtp_hdr->timestamp= htonl(g_time_stamp_current);
Send_RTP_Packet(buf, sent_bytes);
//copy the tail data to the head of the packet buffer
memmove(&buf[HEADER_LENGTH],&buf[sent_bytes], MAX_RTP_PKT_LENGTH-sent_bytes);
//reset the buffer index to zero
reset_buffer_index();
//reading data into buffer again
fread(&(buf[(MAX_RTP_PKT_LENGTH-sent_bytes)+HEADER_LENGTH]), sent_bytes -HEADER_LENGTH ,1,mpfd);
// sleep g_delay_time msec for sending next picture data
if(rtp_hdr->marker==1) Sleep( g_delay_time );
}while(!feof(mpfd));
closesocket(socket1);
fclose(mpfd);
printf("stream end./n");
}
//==================================================================
unsignedint find_next_start_code(unsignedint *next_start_code_index)//NOTE: all start codes ARE byte-aligned
{
unsignedint byte0=0,byte1=0,byte2=0,byte3=0,startcode=0;
//while not startcode and have not exceeded max packet length
while (g_index_in_packet_buffer< MAX_RTP_PKT_LENGTH)
{
if (buf[g_index_in_packet_buffer+0]== 0
&& buf[g_index_in_packet_buffer+1]== 0
&& buf[g_index_in_packet_buffer+2]==1)
{
//printf("FOUND startcode %d/n",indx);
byte0=(int)buf[g_index_in_packet_buffer+0];
byte1=(int)buf[g_index_in_packet_buffer+1];
byte2=(int)buf[g_index_in_packet_buffer+2];
byte3=(int)buf[g_index_in_packet_buffer+3];
startcode=(byte0<< 24)+ (byte1 << 16)+ (byte2 << 8)+ byte3;
*next_start_code_index= g_index_in_packet_buffer;
g_index_in_packet_buffer= g_index_in_packet_buffer+4;
return(startcode);
}
else
g_index_in_packet_buffer++;
}
//<! reach the end of the packet buffer
if (g_index_in_packet_buffer>= (MAX_RTP_PKT_LENGTH))
{
*next_start_code_index= g_index_in_packet_buffer-1;
g_index_in_packet_buffer= HEADER_LENGTH;
return PACKET_BUFFER_END;
}
printf("Error reading buffer../n");
exit(-1);
return -1;
}
void reset_buffer_index(void)
{
g_index_in_packet_buffer= HEADER_LENGTH;
}
//========================================================
float frame_rate(int buffer_index)
{
unsignedchar frame_rate_code;
frame_rate_code= (unsignedchar)buf[buffer_index+7]& 0xf;
switch(frame_rate_code)
{
case 0x1:
return 23.976;
case 0x2:
return 24.0;
case 0x3:
return 25.0;
case 0x4:
return 29.97;
case 0x5:
return 30.0;
case 0x6:
return 50.0;
case 0x7:
return 59.94;
case 0x8:
return 60.0;
default:
return 0;
}
}
//========================================================
unsignedint extract_temporal_reference(int buffer_index)// 10 bits
{
unsignedint low2bits=0,TR=0;// TR = temporal reference;
TR= (unsignedint) (buf[buffer_index+4]);
TR<<= 2;
low2bits= (unsignedint) (buf[buffer_index+5]);
TR|= (low2bits>> 6);
return(TR);
}
//========================================================
unsignedint read_picture_type(int buffer_index)
{
unsignedint pictype=0;
pictype= (unsignedint) buf[buffer_index+5];
pictype= (pictype >> 3)& (0x7);
return (pictype);
}
//=======================================================
unsignedint read_FFV(int buffer_index)// 1 bit
{
return( (int) ((buf[buffer_index+7]& (0x4))>> 2));
}
//=======================================================
unsignedint read_FFC(int buffer_index)// 3 bits
{
unsignedint FFC=0,lowbit=0;
FFC= (int) (buf[buffer_index+7]& (0x3));
FFC<<= 1;
lowbit= (int) ((buf[buffer_index+8])& (0x80));
FFC= FFC | (lowbit >> 7 );
return(FFC);
}
//=======================================================
unsignedint read_FBV(int buffer_index)// 1 bit
{
return( (int) ((buf[buffer_index+8]& (0x40))>>6) );
}
//=======================================================
unsignedint read_BFC(int buffer_index)// 3 bits
{
return( (int) ( (buf[buffer_index+8]& (0x38) )>> 3 ) );
}
void validate_file()
{
/**//* to validate the file, ensure the existance of a startcode */
int j=0,valid=0;
while ((j++<MAX_RTP_PKT_LENGTH)&& (!valid))
{
if (!((int)buf[j+0]+ (int)buf[j+1])&& (((int)buf[j+2])==1))
valid=1;
}
if (!valid)
{
printf("/nERROR: start code not found. /
/nInput file must be a valid MPEG I file./n");
exit(0);
}
}
BOOL InitWinsock()
{
int Error;
WORD VersionRequested;
WSADATA WsaData;
VersionRequested=MAKEWORD(2,2);
Error=WSAStartup(VersionRequested,&WsaData);//启动WinSock2
if(Error!=0)
{
return FALSE;
}
else
{
if(LOBYTE(WsaData.wVersion)!=2||HIBYTE(WsaData.wHighVersion)!=2)
{
WSACleanup();
return FALSE;
}
}
return TRUE;
}
完成这个测试程序后,我有了很大的信心,又重复看了RFC3550几编,其实,如果你真看了程序,你发现我只发送了RTP,并没有发送RTCP数据包,因此,我们是不能同步多个RTP流的。我没去编码下去,因为我觉得已经够了。这里强调,没用说的RTP没有了RTCP就不行!接下来的工作,就是把这个程序的下层发包函数去掉,采用RTP库JRTPLIB,我觉得这才应该是JRTPLIB的DEMO!如果有人问,就这样的一个程序就能完成任务了,要JRTPLIB干嘛,其实,我不写RTCP相关代码的原因为多个:
1.RTCP里头有很多关于RTCP发送简隔的时间计算,RTP信息的统计,这种操作不是难,而是烦,我不想去写
2.RTCP和RTP一开始出来的时候并不是因为视频的点播等应用的,而是视频会议。RTCP有管理与会者的层面含义,这一功能在很多场合并不会用到。
3.我想简单,没有写多个流间的同步,如一个影片的视频和音频流。这些其实是RTCP来完成的。
我懒得去写,因为这些功作RTP的各个库类都做得很好。我觉得用库的最大优点就在这吧。
- RTP - 视频流广播
- RTP - 视频流广播
- RTP - 视频流广播
- RTP - 视频流广播
- RTP - 视频流广播
- RTP - 视频流广播
- RTP - 视频流广播
- H264视频流的RTP封包
- SIPp之播放rtp语音/视频流
- RTP发送音视频流vlc播放
- ffmpeg 接收解码rtp h264视频流
- H264视频流的RTP封包
- ffmpeg 接收解码rtp h264视频流
- SIPp之播放rtp语音/视频流
- RTP/RTCP 视频数据传输
- RTP/RTCP 视频数据传输
- RTP 视频传输
- RTP/RTCP 视频数据传输
- VC:二进制文件和文本文件
- jQuery全景图
- Singleton
- 有道购物助手脚本版,支持chrome,解决bug【Update 0.03】!
- base-kernel-partion
- RTP - 视频流广播
- C异常处理机制:setjmp和longjmp
- Servlet Listener用法
- zk框架 grid复杂表头处理
- android wifi调试总结 theros AR6K命令小结 android wifi debug
- 虚拟机安装Mac OS X Snow Leopard详细教程
- 验证码破解的办法,简单验证码破解演示
- poj 1247 暴力
- QT:不规则窗口的实现