使用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:[email protected]:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1
//如果需要登录,其中admin:kykj1234表示用户名和登录密码。avformat_open_input打开视频文件,从视频流中解析出部分信息,填充到pFormatCtx中,pFormatCtx非常重要,里面
//不仅包含了视频的分辨率,时间戳等信息,而且包含了相应的解码器的信息
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;
}
//检测文件中的流信息,这个函数为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 << "视频流像素格式:"<pix_fmt<width <<" 高度:"<height<= 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;
}
SDL_Delay(40);//这里的值要合适,用1000/帧率,比如25fps,则设置为1000/25 = 40
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;
}