rtp h264 发送和接收程序的问题

目的

为了自己写一个投屏协议,目前重新启用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新做了一个发送界面,
rtp h264 发送和接收程序的问题_第1张图片
使用vlc 接收
rtp h264 发送和接收程序的问题_第2张图片

你可能感兴趣的:(c++,网络,音视频和c++,java,物联网,ffmpeg,rtp,libx264,qt)