使用FFmpeg解码 丢包 花屏

使用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;
}


  • 1
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值