目的
为了自己写一个投屏协议,目前重新启用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;elsem_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 接收