SkeyeExPlayer(Windows)开发系列之解决ffmpeg接口调用卡住的问题

2023-04-14 11:14:35 浏览数 (1)

在SkeyeExPlayer的开发过程中,经测试发现ffmpeg的读取网络流以及网络数据的接口都有较大概率出现阻塞的问题,ffmpeg也提供了设置阻塞回调或者设置超时等方式来跳出阻塞而不会导致接口永久卡住;而在某些时候,比如,网络断开时间过长的时候,这个时候阻塞回调将不在有用而且阻塞的接口也不再返回数据,出现"永久性"假死的情况,针对这些问题,本文将对其处理方式进行一一讲解。

1.播放器结束时接口导致线程卡住

针对该问题,我们通常可以在ffmpeg的阻塞回调函数中设置退出标志来解决,如下代码所示:

代码语言:txt复制
	//播放器退出状态标志,解除阻塞
	if(pPlayer->player_status & PS_CLOSE)
	{
		return AVERROR_EOF;  
	}

2.播放器因为接口卡住而出现断线

这个问题也就是我们通常情况下所说的断线重连的处理,断线重来你分两步走,第一步,判断出现断线的时机;第二布,断线进行重连的处理;

第一步,通常认定读取的网络流数据丢失一定的时间为断线,阻塞回调函数处理如下:

代码语言:txt复制
	int64_t curTime = av_gettime();
	//5s超时退出  
	if ((curTime - pPlayer->cur_read_time) > pPlayer->reconnect_time * 1000 * 1000)//5秒一次的重连
	{
		pPlayer->error_flag = 1;
		char sErrorInfo[100] = { 0, };
		sprintf(sErrorInfo, "interrupt_cb() enter,流已断开,正在尝试重连......curtime=%lldn", curTime);
		OutputDebugStringA(sErrorInfo);
		return AVERROR_STREAM_NOT_FOUND;//AVERROR_EOF;
	}

cur_read_time在初始读取网络流时去一个当前的时间戳,以及av_read_frame时每一帧进行时间戳的更新,如果过一定的时间仍未更新该值,我们则认定网络已经断开,置error_flag =1进行重连,重连过程如下代码所示:

代码语言:txt复制
	while (!(player->player_status & PS_CLOSE))
	{
		usleep(1000*1000);
		if (player->error_flag>0)//must be sth error
		{
			if (player->player_status & PS_CLOSE)
				goto error_handler;

			player->b_ready = 0;
			player_pause(player);
			usleep(500*1000);

			if (player->player_status & PS_CLOSE)
				goto error_handler;

			int64_t media_duration = -1;
			int64_t media_seek_pos = -1;
			if (player->avformat_context)
				media_duration = (player->avformat_context->duration * 1000 / AV_TIME_BASE);	
			render_getparam(player->render, PARAM_MEDIA_POSITION, &media_seek_pos);
			if (media_seek_pos > 0)
				media_seek_pos  = 500;

			if (player->acodec_context) avcodec_close(player->acodec_context);
			player->acodec_context = NULL;
			if (player->vcodec_context) avcodec_close(player->vcodec_context);
			player->vcodec_context = NULL;
			if (player->avformat_context)
			{
				avformat_close_input(&player->avformat_context);
				avformat_free_context(player->avformat_context);
			}
			player->avformat_context = NULL;

			//   for avdevice
			char          *url = player->file_url;
			AVInputFormat *fmt = NULL;
			void		  *win = player->render_hwnd;

			player->avformat_context = avformat_alloc_context();
			player->avformat_context->interrupt_callback.callback = interrupt_cb;
			player->avformat_context->interrupt_callback.opaque = player;

			// open input file
			AVDictionary *options = NULL;
			//   for find trsp
			if ((strstr(url, "rtsp") == url) || (strstr(url, "RTSP") == url))
			{
				if (player->link_mode == STREAM_LINK_TCP)
					av_dict_set(&options, "rtsp_transport", "tcp", 0);
				else
					av_dict_set(&options, "rtsp_transport", "udp", 0);
			}
			//-- for find trsp
			player->cur_read_time = av_gettime();
			if (avformat_open_input(&player->avformat_context, url, fmt, &options) != 0) 
			{
				continue;
				//goto error_handler;
			}
			if (player->player_status & PS_CLOSE)
				goto error_handler;

			// find stream info
			if (avformat_find_stream_info(player->avformat_context, NULL) < 0) 
			{
				continue;
				//goto error_handler;
			}
			if (player->player_status & PS_CLOSE)
				goto error_handler;

			// set current audio & video stream
			player->astream_index = -1; reinit_stream(player, AVMEDIA_TYPE_AUDIO, 0);
			player->vstream_index = -1; reinit_stream(player, AVMEDIA_TYPE_VIDEO, 0);

			// for audio
			if (player->astream_index != -1)
			{
				arate = player->acodec_context->sample_rate;
				aformat = player->acodec_context->sample_fmt;
				alayout = player->acodec_context->channel_layout;
				//   fix audio channel layout issue
				if (alayout == 0) {
					alayout = av_get_default_channel_layout(player->acodec_context->channels);
				}
				//-- fix audio channel layout issue
			}

			// for video
			if (player->vstream_index != -1) {
				vrate = player->avformat_context->streams[player->vstream_index]->r_frame_rate;
				if (vrate.num / vrate.den >= 100) {
					vrate.num = 25;
					vrate.den = 1;
				}
				player->vcodec_context->pix_fmt = vformat;
				width = player->vcodec_context->width;
				height = player->vcodec_context->height;
			}

#if 0
			// open render
			player->render = render_open(ADEV_RENDER_TYPE_WAVEOUT, arate, (AVSampleFormat)aformat, alayout,
				player->render_mode/*VDEV_RENDER_TYPE_GDI*//*VDEV_RENDER_TYPE_D3D*/, win, vrate, vformat, width, height, player->speed);
			if (player->vstream_index == -1) {
				int effect = VISUAL_EFFECT_WAVEFORM;
				render_setparam(player->render, PARAM_VISUAL_EFFECT, &effect);
			}
#endif
			if (player->player_status & PS_CLOSE)
				goto error_handler;
			player->b_ready = 1;
		}
	}

3.avformat_open_input以及av_read_frame接口出现永久性阻塞的处理

经测试,ffmpeg提供的avformat_open_input以及av_read_frame接口有概率出现永久性阻塞,即回调函数停止工作,该函数永久性不在返回的问题,解决办法就是线程调用(当然正常情况下也一般都是线程调用),然后在播放器停止或者已知为卡住的情况下强制结束线程,需要注意的是强制结束线程可能导致内存等资源访问冲突的问题,需要灵活处理。

0 人点赞