網友連接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; }