网友连接1
网友连接2
https://blog.csdn.net/baidu_31872269/article/details/80801242
功能
OpenCV获取的图像数据为BGR格式,需要转换成YUV格式,再将其编码为h264格式,通过ffmpeg推流
前提
1配置好nginx+rtmp服务器配置
2 ffmpeg版本是2.8
3 opencv库
1原理部分
1-1 视频压缩与编解码的基本原理
https://zhuanlan.zhihu.com/p/67305755
视频信号的表示方法:RGB与YUV
YUV是编码
“Y”表示明亮度(Luminance、Luma),“U”和“V”则是色度(Chrominance、Chroma),包含了色调和饱和度。与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。
在实际的编解码等视频处理的过程中,YUV格式比RGB格式更为常用。在YUV格式中,一个像素由亮度分量和色度分量表示,每一个像素由一个亮度分量Y和两个色度分量U/V组成。亮度分量可以与色度分量一一对应,也可以对色度分量进行采样,即色度分量的总量少于亮度分量。
在YUV中之所以采用这样的方式,主要是因为人的感官对亮度信息的敏感度远高于对色度信息。因此相对于其他像素格式,YUV的最大优势是可以适当降低色度分量的采样率,并保证不对图像造成太大影响。而且,使用这种方式还可以兼容黑白和彩色显示设备。对于黑白显示设备,只需要去除色度分量,只显示亮度分量即可。
问题1 为什么要进行H264编码要先把BGR数据转换成YUV图像格式的才可以?
之所以用YUV格式,是因为图像压缩,是因为要利用人对图像的感觉的生理特性。人对亮度很敏感,对色彩信息相对不太敏感,所以可以把色度的分量减少。在这之后,才是开始进行其他压缩算法的编码。而YUV格式就是把图像用亮度和色度分开表示的格式,所以在这上面很容易去掉色度的数据。
由于人的眼睛对图像的低频特性比如物体的总体亮度之类的信息很敏感,而对图像中的高频细节信息不敏感,因此在传送过程中可以少传或不传送高频信息,只传送低频部分。
1-2视频压缩编码
问题1 为什么要压缩
图像的每个像素的三个颜色分量RGB各需要一个字节表示,那么每一个像素至少需要3字节,分辨率1280×720的图像的大小为2.76M字节。
如果对于同样分辨率的视频,如果帧率为25帧/秒,那么传输所需的码率将达到553Mb/s!如果对于更高清的视频,如1080P、4k、8k视频,其传输码率更是惊人。这样的数据量,无论是存储还是传输都无法承受。因此,对视频数据进行压缩称为了必然之选
问题2 为什么可以压缩
视频信息之所以存在大量可以被压缩的空间,是因为其中本身就存在大量的数据冗余。其主要类型有:
时间冗余:视频相邻的两帧之间内容相似,存在运动关系
空间冗余:视频的某一帧内部的相邻像素存在相似性
编码冗余:视频中不同数据出现的概率不同
视觉冗余:观众的视觉系统对视频中不同的部分敏感度不同
针对这些不同类型的冗余信息,在各种视频编码的标准算法中都有不同的技术专门应对,以通过不同的角度提高压缩的比率。
问题3 有哪些编码?
JPEG
JPEG 是Joint Photographic Experts Group(联合图像专家小组)的缩写,是第一个国际图像压缩标准。
H.261
H.261视频编码标准诞生于1988年,可谓是视频压缩编码发展的第一个里程碑。因为从H.261开始,视频编码方法采用了沿用至今的基于波形的混合编码方法。H.261标准主要目标是用于视频会议和可视电话等高实时性、低码率的视频图像传输场合。
MPEG-2/H.262
MPEG组织于1994年推出MPEG-2压缩标准,以实现视/音频服务与应用互操作的可能性。
H.264/MPEG4 (Part10 AVC)
H.264是由ISO/IEC与ITU-T组成的联合视频组(JVT)制定的新一代视频压缩编码标准。在ISO/IEC中该标准命名为AVC (Advanced Video Coding),作为MPEG-4标准的第10个选项;在ITU-T中正式命名为H.264标准。
H264由于算法优化,可以低于1Mbps的速度实现标清数字图像传送;H265则可以实现利用1~2Mbps的传输速度传送720P(分辨率1280*720)普通高清音视频传送。
#include <opencv2/highgui.hpp> #include <iostream> extern "C" { #include <libswscale/swscale.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } #pragma comment(lib, "swscale.lib") #pragma comment(lib, "avcodec.lib") #pragma comment(lib, "avutil.lib") #pragma comment(lib, "avformat.lib") //#pragma comment(lib,"opencv_world300.lib") using namespace std; using namespace cv; int main(int argc, char *argv[]) { //海康相机的rtsp url char *inUrl = "rtsp://test:test123456@192.168.1.64"; //nginx-rtmp 直播服务器rtmp推流URL char *outUrl = "rtmp://192.168.1.101/live"; //注册所有的编解码器 avcodec_register_all(); //注册所有的封装器 av_register_all(); //注册所有网络协议 avformat_network_init(); VideoCapture cam; Mat frame; namedWindow("video"); //像素格式转换上下文 SwsContext *vsc = NULL; //输出的数据结构 AVFrame *yuv = NULL; //编码器上下文 AVCodecContext *vc = NULL; //rtmp flv 封装器 AVFormatContext *ic = NULL; try { //////////////////////////////////////////////////////////////// /// 1 使用opencv打开rtsp相机 cam.open(inUrl); //cam.open(0); // 本地相机 if (!cam.isOpened()) { throw exception("cam open failed!"); } cout << inUrl << " cam open success" << endl; int inWidth = cam.get(CAP_PROP_FRAME_WIDTH); int inHeight = cam.get(CAP_PROP_FRAME_HEIGHT); int fps = cam.get(CAP_PROP_FPS); fps = 25; ///2 初始化格式转换上下文 vsc = sws_getCachedContext(vsc, inWidth, inHeight, AV_PIX_FMT_BGR24, //源宽、高、像素格式 inWidth, inHeight, AV_PIX_FMT_YUV420P,//目标宽、高、像素格式 SWS_BICUBIC, // 尺寸变化使用算法 0, 0, 0 ); if (!vsc) { throw exception("sws_getCachedContext failed!"); } ///3 初始化输出的数据结构 yuv = av_frame_alloc(); yuv->format = AV_PIX_FMT_YUV420P; yuv->width = inWidth; yuv->height = inHeight; yuv->pts = 0; //分配yuv空间 int ret = av_frame_get_buffer(yuv, 32); if (ret != 0) { char buf[1024] = { 0 }; av_strerror(ret, buf, sizeof(buf) - 1); throw exception(buf); } ///4 初始化编码上下文 //a 找到编码器 AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!codec) { throw exception("Can`t find h264 encoder!"); } //b 创建编码器上下文 vc = avcodec_alloc_context3(codec); if (!vc) { throw exception("avcodec_alloc_context3 failed!"); } //c 配置编码器参数 vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局参数 vc->codec_id = codec->id; vc->thread_count = 8; vc->bit_rate = 50 * 1024 * 8;//压缩后每秒视频的bit位大小 50kB vc->width = inWidth; vc->height = inHeight; vc->time_base = { 1,fps }; vc->framerate = { fps,1 }; //画面组的大小,多少帧一个关键帧 vc->gop_size = 50; vc->max_b_frames = 0; vc->pix_fmt = AV_PIX_FMT_YUV420P; //d 打开编码器上下文 ret = avcodec_open2(vc, 0, 0); if (ret != 0) { char buf[1024] = { 0 }; av_strerror(ret, buf, sizeof(buf) - 1); throw exception(buf); } cout << "avcodec_open2 success!" << endl; ///5 输出封装器和视频流配置 //a 创建输出封装器上下文 ret = avformat_alloc_output_context2(&ic, 0, "flv", outUrl); if (ret != 0) { char buf[1024] = { 0 }; av_strerror(ret, buf, sizeof(buf) - 1); throw exception(buf); } //b 添加视频流 AVStream *vs = avformat_new_stream(ic, NULL); if (!vs) { throw exception("avformat_new_stream failed"); } vs->codecpar->codec_tag = 0; //从编码器复制参数 avcodec_parameters_from_context(vs->codecpar, vc); av_dump_format(ic, 0, outUrl, 1); ///打开rtmp 的网络输出IO ret = avio_open(&ic->pb, outUrl, AVIO_FLAG_WRITE); if (ret != 0) { char buf[1024] = { 0 }; av_strerror(ret, buf, sizeof(buf) - 1); throw exception(buf); } //写入封装头 ret = avformat_write_header(ic, NULL); if (ret != 0) { char buf[1024] = { 0 }; av_strerror(ret, buf, sizeof(buf) - 1); throw exception(buf); } AVPacket pack; memset(&pack, 0, sizeof(pack)); int vpts = 0; for (;;) { ///读取rtsp视频帧,解码视频帧 if (!cam.grab()) { continue; } ///yuv转换为rgb if (!cam.retrieve(frame)) { continue; } imshow("video", frame); waitKey(20); ///rgb to yuv //输入的数据结构 uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 }; //indata[0] bgrbgrbgr //plane indata[0] bbbbb indata[1]ggggg indata[2]rrrrr indata[0] = frame.data; int insize[AV_NUM_DATA_POINTERS] = { 0 }; //一行(宽)数据的字节数 insize[0] = frame.cols * frame.elemSize(); int h = sws_scale(vsc, indata, insize, 0, frame.rows, //源数据 yuv->data, yuv->linesize); if (h <= 0) { continue; } //cout << h << " " << flush; ///h264编码 yuv->pts = vpts; vpts++; ret = avcodec_send_frame(vc, yuv); if (ret != 0) continue; ret = avcodec_receive_packet(vc, &pack); if (ret != 0 || pack.size > 0) { //cout << "*" << pack.size << flush; } else { continue; } //推流 pack.pts = av_rescale_q(pack.pts, vc->time_base, vs->time_base); pack.dts = av_rescale_q(pack.dts, vc->time_base, vs->time_base); pack.duration = av_rescale_q(pack.duration, vc->time_base, vs->time_base); ret = av_interleaved_write_frame(ic, &pack); if (ret == 0) { cout << "#" << flush; } } } catch (exception &ex) { if (cam.isOpened()) cam.release(); if (vsc) { sws_freeContext(vsc); vsc = NULL; } if (vc) { avio_closep(&ic->pb); avcodec_free_context(&vc); } cerr << ex.what() << endl; } getchar(); return 0; }