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(
  • 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;
}