FFmepeg:从摄像头获取RTSP(h264、h265)视频流,解码成YUV并保存成文件

ffmpeg:FFmpeg的名称来自MPEG视频编码标准,前面的“FF”代表“Fast Forward,是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

平时我们下载的电影的文件的后缀(avi,mkv,rmvb等)就是所谓的封装方式,解封装就是将这些封装格式转为压缩的视频数据(h264)和压缩音频数据(aac),解码就是把压缩的视频数据(h264)和压缩音频数据(aac),解码成非压缩的视频颜色数据(YUV、RGB)和非压缩的音频抽样数据(pcm)。编码就是非压缩的视频颜色数据(YUV、RGB)和非压缩的音频抽样数据(pcm)编码成压缩的视频数据(h264)和压缩音频数据(aac)。

一般我们从摄像头RTSP获取得到视频流都是“裸流”,也就是原始数据流。得到的码流一般是h264,或者h265,用av_read_frame()来读取每一帧的数据,数据是存放在结构体AVpack里面。是再把它经过解码成YUV像素数据。数据是存放在结构体AVFrame里面。即把 AVpack 存放的数据 (h264、h265)解码成 AVFrame 存放的数据(YUV),可保存成YUV444、YUV422、YUV420,一般以 YUV420为主。

YUV420格式:Y(亮度)、U(色度)、V(浓度)。Y占8位,U、V每4个点共有一个,共8 + 8 / 4 + 8 / 4 = 12位,即3 / 2byte

YUV420的数据长度:

  • 总数据长度:width * height * 3 / 2 byte
  • Y的数据长度:width * height
  • U的数据长度:width * height / 4
  • V的数据长度:width * height / 4

把视频流编码成YUV并保存成文件的流程:

  • ( av_register_all() 函数在ffmpeg4.0以上版本已经被废弃,所以4.0以下版本就需要注册初始函数)
  • avformat_alloc_context()用来申请AVFormatContext类型变量并初始化默认参数,申请的空间
  •  avformat_open_input();打开网络流或文件流
  • avformat_find_stream_info(;获取视频文件信息
  • avcodec_find_decoder(); 寻找解码
  • avcodec_open2();打开解码
  • av_malloc(sizeof( AVPacket ));  申请 AVPacket 空间,以便存放原始数据   
  • av_frame_alloc();申请 AVFrame 空间,存放YUV 像素数据
  • av_read_frame();读取每一帧的(h264、h265)数据,存放在结构体AVPack里面
  • avcodec_decode_video2();把每一帧(h264、h265)数据解码成YUV,存放在结构体AVFrame里面
  • av_free( packet );  写完之后释放  AVPacket 的空间
  • av_frame_free();释放  AVFrame 的空间
  • avformat_free_context();函数释放空间
  • avformat_close_input();关闭rtsp流

实现过程如下:

#include <stdio.h>
#include <stdlib.h>
 
#ifdef __cplusplus 
extern "C"
{
#endif
	/*Include ffmpeg header file*/
#include <libavformat/avformat.h> 
#include <libavcodec/avcodec.h> 
#include <libswscale/swscale.h> 
 
#ifdef __cplusplus
}
#endif
 
int main()
{
	AVFormatContext *pFormatCtx = NULL;
    AVCodecContext *pCodecCtx = NULL;
	AVCodec *pCodec = NULL;
	AVDictionary *options = NULL;
	AVPacket *packet = NULL;
	AVFrame *pFrameYUV = NULL;
	char filepath[] = "rtsp://admin:hk12345678@172.168.0.161:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1";
	
	//av_register_all();  //函数在ffmpeg4.0以上版本已经被废弃,所以4.0以下版本就需要注册初始函数
	
	av_dict_set(&options, "buffer_size", "1024000", 0); //设置缓存大小,1080p可将值跳到最大
	av_dict_set(&options, "rtsp_transport", "tcp", 0); //以tcp的方式打开,
	av_dict_set(&options, "stimeout", "5000000", 0); //设置超时断开链接时间,单位us
	av_dict_set(&options, "max_delay", "500000", 0); //设置最大时延
	
	pFormatCtx = avformat_alloc_context(); //用来申请AVFormatContext类型变量并初始化默认参数,申请的空间

 
	//打开网络流或文件流
	if (avformat_open_input(&pFormatCtx, filepath, NULL, &options) != 0)
	{
		printf("Couldn't open input stream.\n");
		return 0;
	}
	
	//获取视频文件信息
	if (avformat_find_stream_info(pFormatCtx, NULL)<0)
	{
		printf("Couldn't find stream information.\n");
		return 0;
	}
	
	//查找码流中是否有视频流
	int videoindex = -1;
	unsigned i = 0;
	for (i = 0; i<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");
		return 0;
	}

    pCodecCtx = pFormatCtx->streams[videoindex]->codec;
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);  //寻找解码器
	if (pCodec == NULL)
	{
		printf("Codec not found.\n");
		return -1;
	}
	if (avcodec_open2(pCodecCtx, pCodec, NULL)<0)  //打开解码器
	{
		printf("Could not open codec.\n");
		return -1;
	}
	
	packet = (AVPacket *)av_malloc(sizeof(AVPacket)); // 申请空间,存放的每一帧数据 (h264、h265)
	pFrameYUV = av_frame_alloc(); // 申请空间,存放每一帧编码后的YUV数据
    AVStream *video_stream = pFormatCtx->streams[videoindex];
	
	FILE *fp_YUV = NULL;
    int got_picture = 0;
	fp_YUV = fopen("getYUV.yuv", "wb");

    //这边可以调整i的大小来改变文件中的视频时间,每 +1 就是一帧数据
	for (i = 0; i < 200; i++)   
	{
		if (av_read_frame(pFormatCtx, packet) >= 0)
		{
			if (packet->stream_index == videoindex)
			{      
                avcodec_decode_video2(pCodecCtx,pFrameYUV, &got_picture, packet);
                fwrite(pFrameYUV->data[0], 1, video_stream->codecpar->width*video_stream->codecpar->height,fp_YUV);  //Y
                fwrite(pFrameYUV->data[1], 1, video_stream->codecpar->width*video_stream->codecpar->height/4,fp_YUV);//U
                fwrite(pFrameYUV->data[2], 1, video_stream->codecpar->width*video_stream->codecpar->height/4,fp_YUV);//V
                printf("w: %d, H: %d\n", video_stream->codecpar->width, video_stream->codecpar->height);
			}
			av_packet_unref(packet);
		}
	}
 
	fclose(fp_YUV);
    av_free(packet);
    av_frame_free(&pFrameYUV);
	avformat_close_input(&pFormatCtx);
    
    return 0;
}

 

 

 

  • 7
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
你可以使用FFmpeg来拉取H264视频流并在浏览器播放。以下是一些步骤和方法: 1.使用FFmpeg的avformat_open_input()函数打开视频流。 2.使用avformat_find_stream_info()函数查找视频流的信息。 3.使用avcodec_find_decoder()函数查找解码器。 4.使用avcodec_open2()函数打开解码器。 5.使用av_read_frame()函数读取视频帧。 6.使用avcodec_decode_video2()函数解码视频帧。 7.使用sws_scale()函数将解码后的视频帧转换为RGB格式。 8.使用SDL库或其他库将RGB格式的视频帧显示在屏幕上。 以下是一个简单的C语言程序,可以使用FFmpeg拉取H264视频流并将其显示在屏幕上: <<引用:>> gcc VedioToH264_demo.c -o ffmpegtest -I /home/xy/ffmpeg/ffmpeg-4.0.2/out/include/ -L /home/xy/ffmpeg/ffmpeg-4.0.2/out/lib/ -lavformat -lavcodec -lavutil -lswresample -lm -pthread -lrt -ldl -lz -lX11 -lvdpau <<代码>> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <SDL/SDL.h> #include <SDL/SDL_thread.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #define SDL_AUDIO_BUFFER_SIZE 1024 #define MAX_AUDIO_FRAME_SIZE 192000 int quit = 0; void quit_signal(int signo) { quit = 1; } int main(int argc, char *argv[]) { AVFormatContext *pFormatCtx = NULL; int i, videoStream; AVCodecContext *pCodecCtxOrig = NULL; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFrame *pFrame = NULL; AVPacket packet; int frameFinished; struct SwsContext *sws_ctx = NULL; SDL_Surface *screen = NULL; SDL_Overlay *bmp = NULL; SDL_Rect rect; SDL_Event event; SDL_Thread *video_tid = NULL; int videoWidth, videoHeight; Uint32 pixformat; char *filename = "rtsp://192.168.1.100:554/av0_0"; int ret; signal(SIGINT, quit_signal); signal(SIGTERM, quit_signal); av_register_all(); avformat_network_init(); pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { fprintf(stderr, "Cannot open input file\n"); return -1; } if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { fprintf(stderr, "Cannot find stream information\n"); return -1; } videoStream = -1; for (i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } if (videoStream == -1) { fprintf(stderr, "Cannot find a video stream\n"); return -1; } pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec; pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id); if (pCodec == NULL) { fprintf(stderr, "Unsupported codec\n"); return -1; } pCodecCtx = avcodec_alloc_context3(pCodec); if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar) < 0) { fprintf(stderr, "Failed to copy codec parameters to decoder context\n"); return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { fprintf(stderr, "Failed to open codec\n"); return -1; } pFrame = av_frame_alloc(); screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0); if (!screen) { fprintf(stderr, "SDL: could not set video mode - exiting\n"); exit(1); } bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen); sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL); video_tid = SDL_CreateThread(video_thread, NULL); while (!quit) { SDL_WaitEvent(&event); switch (event.type) { case SDL_QUIT: quit = 1; break; default: break; } } SDL_Quit(); avcodec_close(pCodecCtx); av_free(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; } int video_thread(void *arg) { AVFormatContext *pFormatCtx = NULL; int i, videoStream; AVCodecContext *pCodecCtxOrig = NULL; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFrame *pFrame = NULL; AVPacket packet; int frameFinished; struct SwsContext *sws_ctx = NULL; SDL_Surface *screen = NULL; SDL_Overlay *bmp = NULL; SDL_Rect rect; SDL_Event event; int videoWidth, videoHeight; Uint32 pixformat; char *filename = "rtsp://192.168.1.100:554/av0_0"; int ret; av_register_all(); avformat_network_init(); pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { fprintf(stderr, "Cannot open input file\n"); return -1; } if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { fprintf(stderr, "Cannot find stream information\n"); return -1; } videoStream = -1; for (i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } if (videoStream == -1) { fprintf(stderr, "Cannot find a video stream\n"); return -1; } pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec; pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id); if (pCodec == NULL) { fprintf(stderr, "Unsupported codec\n"); return -1; } pCodecCtx = avcodec_alloc_context3(pCodec); if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar) < 0) { fprintf(stderr, "Failed to copy codec parameters to decoder context\n"); return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { fprintf(stderr, "Failed to open codec\n"); return -1; } pFrame = av_frame_alloc(); screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0); if (!screen) { fprintf(stderr, "SDL: could not set video mode - exiting\n"); exit(1); } bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen); sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL); while (av_read_frame(pFormatCtx, &packet) >= 0) { if (packet.stream_index == videoStream) { ret = avcodec_send_packet(pCodecCtx, &packet); if (ret < 0) { fprintf(stderr, "Error sending a packet for decoding\n"); break; } while (ret >= 0) { ret = avcodec_receive_frame(pCodecCtx, pFrame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { fprintf(stderr, "Error during decoding\n"); break; } sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, bmp->pixels, bmp->pitches); rect.x = 0; rect.y = 0; rect.w = pCodecCtx->width; rect.h = pCodecCtx->height; SDL_DisplayYUVOverlay(bmp, &rect); } } av_packet_unref(&packet); if (quit) { break; } } avcodec_close(pCodecCtx); av_free(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值