使用FFmpeg解码 丢包 花屏

原创 2016年08月29日 23:49:27

使用FFmpeg解码,使用SDL显示画面有时候会连续丢包,导致花屏的现象。


解决办法:

1.将rtsp传输方式由默认的udp传输,设置为tcp传输

2.每次解码一帧后,SDL_Delay时长设置为跟帧率匹配,使用1000/帧率作为时长

发现就没有频繁丢包的情况了

代码如下:

DWORD WINAPI ONVIF::ShowVideo(void *param)
{
	if(param == NULL){
		MW_DEBUG_ERR("param is err!\n");
		return -1;//
	}
	AVFormatContext *pFormatCtx=NULL;  
	int             i = 0, videoindex = 0 , y_size = 0 , ret = 0;
	AVCodecContext  *pCodecCtx=NULL;  
	AVCodec         *pCodec=NULL;  
	AVFrame *pFrame=NULL,*pFrameYUV=NULL;  
	unsigned char *out_buffer=NULL;  
  	SwsContext *img_convert_ctx1;//用于YUV420p
	SwsContext *img_convert_ctx2;//用于BGR24
	AVPacket packet;

	VideoInfo vInfo;
	memcpy(&vInfo,param,sizeof(VideoInfo));
	char filepath[MAX_PATH] = {0};
	strcpy_s(filepath,vInfo.uri);
	MW_DEBUG_INFO("show video param:\n");
	MW_DEBUG_INFO("uri:%s\nx:%d\ny:%d\nheight:%d\nwidth:%d\nisShowVideo:%d\n",
		vInfo.uri,vInfo.x,vInfo.y,vInfo.height,vInfo.width,vInfo.isShowVideo);

  
	//视频流处理其实就是从文件中读取一包包的packet,将这一包包的packet组成frame帧
	av_register_all(); //注册所有文件格式和编解码的库,初始化库,你也可以初始化让它仅仅支持某一种编码格式,但是没必要
	avformat_network_init(); //是能网络功能,可以从网络上读取流数据 
	pFormatCtx = avformat_alloc_context();//分配空间,主要存储视音频封装格式中包含的信息
	
	//读取文件(或者rtsp地址)信息的头部,并且把信息保存在pFormatCtx中,后面两个NULL用来指定特殊文件格式,这里表示自动检测文件格式
	//打开视频文件,文件名可以是一个rtsp视频流地址,例如rtsp://admin:kykj1234@192.168.1.222:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1
	//如果需要登录,其中admin:kykj1234表示用户名和登录密码。avformat_open_input打开视频文件,从视频流中解析出部分信息,填充到pFormatCtx中,pFormatCtx非常重要,里面
	//不仅包含了视频的分辨率,时间戳等信息,而且包含了相应的解码器的信息
<span style="white-space:pre">	</span><span style="color:#ff0000;">AVDictionary* options = NULL;  
	av_dict_set(&options, "rtsp_transport", "tcp", 0);  
	if(avformat_open_input(&pFormatCtx,filepath,NULL/*自动检测文件格式*/,&options)!=0){
		MW_DEBUG_ERR("Couldn't open input stream : %s\n",filepath);  
		goto END;
		return -1;  
	}</span>


	//检测文件中的流信息,这个函数为pFormatCtx->streams流信息数据成员填充上正确的信息
	if(avformat_find_stream_info(pFormatCtx,NULL)<0){
		MW_DEBUG_ERR("Couldn't find stream information.\n");
		goto END;
		return -1;
	}

	//找到第一个视频流,因为里面的流还有可能是音频流或者其他的,我们摄像头只关心视频流
	videoindex=-1;
	for(i=0; i<(int)pFormatCtx->nb_streams; i++){
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
			videoindex=i;
			break;
		}
	}
	if(videoindex==-1){//没有找到视频流
		printf("Didn't find a video stream.\n");
		goto END;
		return -1;
	}
	
	//获取一个合适的编码器pCodec
	pCodecCtx=pFormatCtx->streams[videoindex]->codec;  
	pCodec=avcodec_find_decoder(pCodecCtx->codec_id);  
	if(pCodec==NULL){  
		printf("Codec not found.\n"); 
		goto END;
		return -1;  
	}  
	//打开这个编码器,pCodecCtx表示编码器上下文,里面有流数据的信息
	if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){  
		printf("Could not open codec.\n");  
		return -1;  
	}  
    
	//pFrameYUV分配空间,该函数并没有为AVFrame的像素数据分配空间,需要使用av_image_fill_arrays分配
	pFrameYUV = av_frame_alloc();
	if(pFrameYUV == NULL){
		goto END;
		return -1;
	}
	int numBytes;
	numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUVJ420P , pCodecCtx->width , pCodecCtx->height , 1);
	out_buffer = (uint8_t*)av_malloc(sizeof(uint8_t) * numBytes);
	//avpicture_fill((AVPicture*)pFrameYUV , out_buffer , AV_PIX_FMT_YUVJ420P,pCodecCtx->width , pCodecCtx->height);
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,  
        AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);  
	
	SDL_Window *window = nullptr;//对应的就是原来的SDL_Surface
	SDL_Renderer *ren = nullptr;
	SDL_Rect rect;
	SDL_Texture *texture = NULL;
	if(vInfo.isShowVideo){
		/*====================SDL init start=====================*/
		if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {    
			printf( "Could not initialize SDL - %s\n", SDL_GetError());   
			goto END;
			return -1;  
		}   
	
	
		window = SDL_CreateWindow("NetCamera", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
			vInfo.width/*pCodecCtx->width*/,vInfo.height /*pCodecCtx->height*/, SDL_WINDOW_OPENGL);
		if (!window){
			cout << SDL_GetError() << endl;
			goto END;
			return -1;
		}
		SDL_SetWindowBordered(window ,SDL_FALSE);
		SDL_SetWindowPosition(window,vInfo.x,vInfo.y);
	
		//创建渲染器,渲染器和窗口联系起来了
		ren = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
		if (ren == nullptr){
			cout << SDL_GetError() << endl;
			goto END;
			return -1;
		}

	
		//创建文理,文理和渲染器联系起来了,一个文理对应一帧图片数据
		texture = SDL_CreateTexture(ren, SDL_PIXELFORMAT_YV12,
		SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
	
		rect.x = 0, rect.y = 0;
		rect.w = pCodecCtx->width;
		rect.h = pCodecCtx->height;
	}

	//*************************************************************//
	//通过读取包来读取整个视频流,然后把它解码成帧,最后转换格式并且保存
	int frameFinished;
	//int psize = pCodecCtx->width * pCodecCtx->height;
	//分配一个packet,用于存储从视频流中读取的原始的还没有解码的数据,大小刚好为一帧
	av_new_packet(&packet, numBytes);
	av_init_packet(&packet);
	//output file information
	cout << "-----------------------------文件信息----------------------------------" << endl;
	av_dump_format(pFormatCtx, 0, filepath, 0);
	cout << "-----------------------------------------------------------------------" << endl;

	i = 0;
	int tmp_width = vInfo.width,tmp_height = vInfo.height;
	if(-1 == vInfo.width){
		tmp_width = pCodecCtx->width;
	}
	if(-1 == vInfo.height){
		tmp_height = pCodecCtx->height;
	}
	//sws_getContext是初始化函数,初始化你需要转换的格式,目的是为了获取返回的SwsContext指针变量,给后面的sws_scale使用
	//sws_scale会根据你初始化的信息来转换视频格式,可以改变视频格式,也可以改变视频的分辨率,因为如果想要窗口缩小,需要将
	//分辨率改成相应大小
	//1.这里是将解码后的视频流转换成YUV420P
	img_convert_ctx1 = sws_getContext(pCodecCtx->width/*视频宽度*/, pCodecCtx->height/*视频高度*/,
		pCodecCtx->pix_fmt/*像素格式*/, tmp_width/*pCodecCtx->width*//*目标宽度*/,tmp_height/* pCodecCtx->height*//*目标高度*/, AV_PIX_FMT_YUV420P/*目标格式*/,
	SWS_BICUBIC/*图像转换的一些算法*/, NULL, NULL, NULL);




//===============================================start=================================================
	//pFrameRGB分配空间,该函数并没有为AVFrame的像素数据分配空间,需要使用av_image_fill_arrays分配
	AVFrame *pFrameRGB = av_frame_alloc();
	if(pFrameRGB == NULL){
		goto END;
		return -1;
	}
	int rgbSize;
	rgbSize = av_image_get_buffer_size(AV_PIX_FMT_BGR24 ,tmp_width/* pCodecCtx->width*/ ,tmp_height /*pCodecCtx->height*/ , 1);
	unsigned char *rgb_buffer = (uint8_t*)av_malloc(sizeof(uint8_t) * rgbSize);
	av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize,rgb_buffer,  
        AV_PIX_FMT_BGR24 ,tmp_width, tmp_height,1); 
	//2.这里是将解码后的视频流转换成RGB
	img_convert_ctx2 = sws_getContext(pCodecCtx->width/*视频宽度*/, pCodecCtx->height/*视频高度*/,
		pCodecCtx->pix_fmt/*像素格式*/, tmp_width/*pCodecCtx->width*//*目标宽度*/,tmp_height/* pCodecCtx->height*//*目标高度*/,
		AV_PIX_FMT_BGR24/*目标格式*/,SWS_BICUBIC/*图像转换的一些算法*/, NULL, NULL, NULL);
	cout << "视频流像素格式:"<<pCodecCtx->pix_fmt<<endl;
	cout << "宽度:"<<pCodecCtx->width <<" 高度:"<<pCodecCtx->height<<endl;
	cout << "rgbsize:"<<rgbSize<<endl;
//===============================================end=================================================



	pFrame=av_frame_alloc();//分配帧内存,用来保存帧,存储从packet解码后的帧数据
	if(pFrame == NULL){
		goto END;
	}
	//Read the next frame of a stream
	while (av_read_frame(pFormatCtx, &packet) >= 0)
	{//读取原始数据(此时还没有解码)放到packet中
		if(b_exit_thread){
			break;
		}
		EventProc();//在显示视频的线程里面必须要相应事件处理,否则一旦点击窗口会卡死
		//Is this a packet from the video stream?
		//如果这个是一个视频流数据
		if (packet.stream_index == videoindex){
			//decode video frame of size packet.size from packet.data into picture
			//解码一帧视频数据,在这个里面把packet数据解码放到了pFrame中
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

			//Did we get a video frame?
			if (ret >= 0){
				//Convert the image from its native format to YUV
				if (0 != frameFinished){//这个标志表示已经都去了一个完整帧,因为读取一个packet不一定就是一个完整帧,如果不完整需要继续读取packet
					
//===============================================start 开始将YUV数据转换成RGB并保存图片==============
					//将视频流数据转换成RGB格式
					//pFrame->data[0] += pFrame->linesize[0] * (pCodecCtx->height - 1);
					//pFrame->linesize[0] *= -1;
					//pFrame->data[1] += pFrame->linesize[1] * (pCodecCtx->height / 2 - 1);
					//pFrame->linesize[1] *= -1;
					//pFrame->data[2] += pFrame->linesize[2] * (pCodecCtx->height / 2 - 1);
					//pFrame->linesize[2] *= -1;
					int h = sws_scale(img_convert_ctx2 ,(const uint8_t* const*)pFrame->data/*源数据地址*/,
						pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data , pFrameRGB->linesize);


					pthread_mutex_lock (&mutex);
					ret = SaveBmp24(NULL,tmp_width,tmp_height,pFrameRGB->data[0]);
					pthread_mutex_unlock(&mutex);

//===============================================end=================================================

					
					/*格式转换函数,可以转换视频格式,也可以用来改变视频的分辨率*/
					sws_scale(img_convert_ctx1, (const uint8_t* const*)pFrame->data/*源数据地址*/,
					pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data/*目标数据地址*/, pFrameYUV->linesize);

					if(vInfo.isShowVideo){
						SDL_UpdateYUVTexture(texture, &rect, 
							pFrameYUV->data[0], pFrameYUV->linesize[0],//Y分量
							pFrameYUV->data[1], pFrameYUV->linesize[1],//U分量
							pFrameYUV->data[2], pFrameYUV->linesize[2]);//V分量
					
						SDL_RenderClear(ren);
						SDL_RenderCopy(ren, texture, &rect, &rect);
						SDL_RenderPresent(ren);
						//SDL_Delay(10);//这里的值不能太大,太大的话会出现花屏的现象
					}
				}
				
			}else{
				cout << "decode failed" << endl;
				goto END;
			}
		<span style="color:#ff0000;">SDL_Delay(40);//这里的值要合适,用1000/帧率,比如25fps,则设置为1000/25 = 40</span>
		av_packet_unref(&packet);
	}

END:
	if(NULL != out_buffer){
		av_free(out_buffer);
		out_buffer = NULL;
	}
	if(NULL != pFrame){
		av_frame_free(&pFrame);
		pFrame = NULL;
	}
	if(NULL != pFrameYUV){
		av_frame_free(&pFrameYUV);
		pFrameYUV = NULL;
	}
	if(NULL != pCodecCtx){
		avcodec_close(pCodecCtx);
		pCodecCtx = NULL;
	}
	if(NULL != pCodec){
		
	}
	if(NULL != pFormatCtx){
		avformat_close_input(&pFormatCtx);
		pFormatCtx = NULL;
	}
	if(NULL != img_convert_ctx1){
		sws_freeContext(img_convert_ctx1);
		img_convert_ctx1 = NULL;
	}
	if(NULL != img_convert_ctx2){
		sws_freeContext(img_convert_ctx2);
		img_convert_ctx1 = NULL;
	}
	if(NULL != window){
		SDL_DestroyWindow(window);
		window = NULL;
	}
	if(NULL != ren){
		SDL_DestroyRenderer(ren);
		ren = NULL;
	}
	if(NULL != texture){
		SDL_DestroyTexture(texture);
		texture = NULL;
	}
	if(NULL != packet.data){
		av_free_packet(&packet);
	}
	b_exit_thread_finished = true;//说明线程退出完毕
	return 0;
}


相关文章推荐

ffmpeg解码花屏

问题: 解码为YUV420转为Bitmap后显示在屏幕上时,有三分之二为花屏:如图: 首先用h264Visa分析帧: 已经读出了sps等信息,这些信在解码第一帧时被写入环境变量中...

FFmpeg相机花屏花图问题解决方法

解决FFmpeg解码rtsp花图

用ffmpeg提取mp4的h264码流写文件花屏

1、用ffmpeg提取mp4的h264码流写文件的方法网上有很多,不知道的请参考雷神博客:http://blog.csdn.net/leixiaohua1020/article/details/118...

【FFmpeg】ffplay播放rtsp视频流花屏问题

http://www.cnblogs.com/dwdxdy/p/3240217.html 问题描述:ffplay播放rtsp视频流时,播放过程中随机出现花屏现象。 基本流程学习:阅读ff...

ffmpeg h264解码, 屏蔽因为网络丢包等各种原因导致的花屏帧

ffmpeg h264解码, 屏蔽因为网络丢包等各种原因导致的花屏帧  ---->看来问题只能这样解决了,现在还要多测测,防止产生新的问题。目前来看,对现有代码没有影响,花屏的帧直接屏蔽掉了。 ...

【FFmpeg】ffplay播放rtsp视频流花屏问题

【FFmpeg】ffplay播放rtsp视频流花屏问题 问题描述:ffplay播放rtsp视频流时,播放过程中随机出现花屏现象。 基本流程学习:阅读ffplay源码,熟悉其播放rtsp...
  • zds05
  • zds05
  • 2017年02月09日 15:23
  • 413

ffmpeg播放rtsp视频流花屏解决办法

http://blog.sina.com.cn/s/blog_9e7753030102vd5s.html

FFMPEG 实现 YUV,RGB各种图像原始数据之间的转换(swscale)

FFMPEG中的swscale提供了视频原始数据(YUV420,YUV422,YUV444,RGB24...)之间的转换,分辨率变换等操作,使用起来十分方便,在这里记录一下它的用法。 swscale...

最简单的基于FFmpeg的AVfilter的例子-纯净版

有关FFmpeg的avfilter已经写过一个水印叠加的例子《最简单的基于FFmpeg的AVfilter例子(水印叠加)》,本文作为补充再记录一个纯净版的avfilter的例子。此前libavfilt...

FFmpeg API 变更记录

最近一两年内FFmpeg项目发展的速度很快,本来是一件好事。但是随之而来的问题就是其API(接口函数)一直在发生变动。这么一来基于旧一点版本的FFmpeg的程序的代码在最新的类库上可能就跑不通了。例如...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用FFmpeg解码 丢包 花屏
举报原因:
原因补充:

(最多只允许输入30个字)