目的
为了自己写一个投屏协议,目前重新启用rtp协议,使用rtp协议直传效率最高,并且使用的是udp
ffmpeg 发送rtp
ffmpeg的rtp发送时一般把sps和pps放在一个包里面,写接收代码的时候要注意,在单包里面可以直接接收到两个,不过自己写的代码就不一样了,很多人喜欢把sps和pps 分开来发送,写错了,就会丢包
libx264 和 ffmpeg编码发送
直接使用libx264发送
int X264Encoder::Encode4RTP(unsigned char * szYUVFrame, bool &isKeyframe)
{
int numPixels = m_param.i_width * m_param.i_height;
m_pic.img.plane[0] = szYUVFrame;
m_pic.img.plane[1] = szYUVFrame + numPixels;
m_pic.img.plane[2] = szYUVFrame + numPixels * 5 / 4;
m_pic.img.i_stride[0] = m_param.i_width;
m_pic.img.i_stride[1] = m_param.i_width / 2;
m_pic.img.i_stride[2] = m_param.i_width / 2;
m_param.i_frame_total++;
m_pic.i_pts = (int64_t)m_param.i_frame_total * m_param.i_fps_den;
//dts赋值和pts相同的值
m_pic.i_dts = m_pic.i_pts;
if (isKeyframe)
m_pic.i_type = X264_TYPE_IDR;
else
m_pic.i_type = X264_TYPE_AUTO;
int i_frame_size = x264_encoder_encode(m_h, &_naluIter_t, &_naluIter, &m_pic, &_pic_out);
if (i_frame_size > 0)
{
isKeyframe = (_pic_out.i_type == X264_TYPE_IDR);
return 0;
}
return -1;
}
初始化 x264encoder, 发送rtp的关键在于每个关键帧的前面有sps pps, 实际上就是让
m_param.b_repeat_headers = 1;
不过默认值就是1 不用设置。
int X264Encoder::Initialize(int iWidth, int iHeight, int iRateBit, int iFps)
{
x264_param_default_preset(&m_param, "ultrafast", "zerolatency");
m_param.i_csp = X264_CSP_I420;
m_param.i_width = iWidth;
m_param.i_height = iHeight;
m_param.i_fps_num = iFps;
m_param.i_fps_den = 1;
m_param.rc.i_bitrate = iRateBit;
m_param.rc.i_rc_method = X264_RC_ABR;
m_param.i_frame_reference = 2; /* 参考帧的最大帧数 */
//m_param.i_keyint_max = 8;
//m_param.i_keyint_min = 4;
m_param.i_frame_total = 0;
m_param.i_bframe = 0;
m_param.i_threads = 1;
m_param.rc.i_lookahead = 0;
//m_param.i_sync_lookahead = X264_SYNC_LOOKAHEAD_AUTO;
m_param.i_sync_lookahead = 0;
m_param.b_cabac = 1;
m_param.analyse.b_transform_8x8 = 1;
//m_param.b_repeat_headers = 1;
m_param.i_level_idc = 12;
//x264_param_apply_profile(&m_param, x264_profile_names[0]);
m_param.i_log_level = X264_LOG_NONE;//X264_LOG_WARNING;// X264_LOG_ERROR;//X264_LOG_NONE;
x264_param_apply_profile(&m_param, "baseline");
/* 根据输入参数param初始化总结构 x264_t *h */
if( ( m_h = x264_encoder_open( &m_param ) ) == NULL )
{
//fprintf( stderr, "x264 [error]: x264_encoder_open failed\n" );
return -1;
}
//x264_picture_alloc( &m_pic, X264_CSP_I420, m_param.i_width, m_param.i_height );
x264_picture_init(&m_pic);
memset(&m_pic, 0, sizeof(x264_picture_t));
m_pic.i_type = X264_TYPE_AUTO;
m_pic.i_qpplus1 = 0;
m_pic.img.i_csp = X264_CSP_I420;
m_pic.img.i_plane = 3;
return 0;
}
ok,怎么发送比较效率高怎么改代码
ffmpeg 编码发送
对于ffmpeg来说,比较关键的就是 AV_CODEC_FLAG_GLOBAL_HEADER,这个值就是决定param中是否repeat header, 也就是:是否在关键帧前面加sps 和pps,如果是rtmp协议,我们加上,而rtp协议,我们不加。 这样在
_vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局参数
每个关键帧的前面都会加上sps和pps,这样使用vlc 这种工具写一个sdp文件,打开的时候碰到关键帧就可以解码出来。
时间戳
注意时间戳的基数是90000,为什么使用90000,因为这个数字让一些比较奇怪的帧数间隔时间也能成为整数。
发送事件间隔
这个太关键了,发送的时候一定要按照正式的帧率来发送,以下为示例代码
代码使用libx264 进行编码,当然使用openh254 , ffmpeg也是一样的,计算好时间间隔发送,如果为20帧,那就要每帧间隔50毫秒。
void VideoEncoderThread::Run()
{
// 开始循环获取每一帧,并编码
unsigned int timestamp = 0;
unsigned int last_idr_timestamp = 0;
bool is_first = true;
//int x264buf_len = 1024 * 512;
//unsigned char* x264buf = (unsigned char*)malloc(x264buf_len);
int inter = 1000 / v_fps; // 50;
while (false == IsStop())
{
//char* rgbbuf = ds_video_graph_->GetBuffer();
unsigned int now_tick = ::GetTickCount();
unsigned int next_tick = now_tick + inter;
bool is_keyframe = false;
timestamp = now_tick;
if (timestamp - last_idr_timestamp >= 2000 || last_idr_timestamp ==0 )
{
is_keyframe = true;
last_idr_timestamp = timestamp;
}
{
std::unique_lock<std::mutex> lock(v_mt);
x264_encoder_->Encode4RTP(v_yuvbuf, is_keyframe);
}
if (ok)
{
int num = x264_encoder_->GetNaluNumber();
int size = 0;
for (int i = 0; i < num; i++)
{
uint8_t *buf = x264_encoder_->GetNalu(i, size);
sendrtp(buf, size, timestamp, is_keyframe);
}
}
now_tick = ::GetTickCount();
if (next_tick > now_tick)
{
Sleep(next_tick - now_tick);
}
}
//free(yuvbuf);
//free(x264buf);
//free(encoder_rgbbuf);
/* if (fp_264)
{
fclose(fp_264);
}*/
}
摄像头问题
摄像头问题就很多了,web摄像头 也就是usb摄像头等什么时候拿到一帧是不确定的,就算是设定20帧,也未一秒钟必能拿到20帧,所以当中的缓存至关重要,如果不够,就要加帧,
1简单的做法 : 使用上一帧加帧
2 使用中间缓存,定时取帧,不要管上一帧下一帧是否相同,但是中间缓存要枷锁。
sdp 文件
v=0
m=video 6000 RTP/AVP 96
a=rtpmap:96 H264/90000
c=IN IP4 127.0.0.1
写好rtp 发送以后, 将以上文件存成test.sdp, 任何时候直接用vlc打开sdp文件 ,就可以看到图像,以上是在本机的6000端口 等待数据
界面显示
为了跨平台,用qt新做了一个发送界面,
使用vlc 接收