雖然FFmpeg自己有cuvid硬解,可是找不到什麼好的資料,英偉達的SDK比較容易懂,參考FFmpeg源碼,將NVIDIA VIDEO CODEC SDK的數據獲取改成FFmpeg獲取,彌補原生SDK不能以流做爲數據源的不足。所用SDK版本爲Video_Codec_SDK_7.1.9,英偉達官網可下載。ide
首先是FFmpeg的一些常規的初始化函數
bool VideoSource::init(const std::string sFileName, FrameQueue *pFrameQueue) { assert(0 != pFrameQueue); oSourceData_.hVideoParser = 0; oSourceData_.pFrameQueue = pFrameQueue; int i; AVCodec *pCodec; av_register_all(); avformat_network_init(); pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, sFileName.c_str(), NULL, NULL) != 0){ printf("Couldn't open input stream.\n"); return false; } if (avformat_find_stream_info(pFormatCtx, NULL)<0){ printf("Couldn't find stream information.\n"); return false; } videoindex = -1; 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 false; } pCodecCtx = pFormatCtx->streams[videoindex]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if (pCodec == NULL){ printf("Codec not found.\n"); return false; } //Output Info----------------------------- printf("--------------- File Information ----------------\n"); av_dump_format(pFormatCtx, 0, sFileName.c_str(), 0); printf("-------------------------------------------------\n"); memset(&g_stFormat, 0, sizeof(CUVIDEOFORMAT)); switch (pCodecCtx->codec_id) { case AV_CODEC_ID_H263: g_stFormat.codec = cudaVideoCodec_MPEG4; break; case AV_CODEC_ID_H264: g_stFormat.codec = cudaVideoCodec_H264; break; case AV_CODEC_ID_HEVC: g_stFormat.codec = cudaVideoCodec_HEVC; break; case AV_CODEC_ID_MJPEG: g_stFormat.codec = cudaVideoCodec_JPEG; break; case AV_CODEC_ID_MPEG1VIDEO: g_stFormat.codec = cudaVideoCodec_MPEG1; break; case AV_CODEC_ID_MPEG2VIDEO: g_stFormat.codec = cudaVideoCodec_MPEG2; break; case AV_CODEC_ID_MPEG4: g_stFormat.codec = cudaVideoCodec_MPEG4; break; case AV_CODEC_ID_VP8: g_stFormat.codec = cudaVideoCodec_VP8; break; case AV_CODEC_ID_VP9: g_stFormat.codec = cudaVideoCodec_VP9; break; case AV_CODEC_ID_VC1: g_stFormat.codec = cudaVideoCodec_VC1; break; default: return false; } //這個地方的FFmoeg與cuvid的對應關係不是很肯定,不過用這個參數彷佛最靠譜 switch (pCodecCtx->sw_pix_fmt) { case AV_PIX_FMT_YUV420P: g_stFormat.chroma_format = cudaVideoChromaFormat_420; break; case AV_PIX_FMT_YUV422P: g_stFormat.chroma_format = cudaVideoChromaFormat_422; break; case AV_PIX_FMT_YUV444P: g_stFormat.chroma_format = cudaVideoChromaFormat_444; break; default: g_stFormat.chroma_format = cudaVideoChromaFormat_420; break; } //找了很久,總算是找到了FFmpeg中標識場格式和幀格式的標識位 //場格式是隔行掃描的,須要作去隔行處理 switch (pCodecCtx->field_order) { case AV_FIELD_PROGRESSIVE: case AV_FIELD_UNKNOWN: g_stFormat.progressive_sequence = true; break; default: g_stFormat.progressive_sequence = false; break; } pCodecCtx->thread_safe_callbacks = 1; g_stFormat.coded_width = pCodecCtx->coded_width; g_stFormat.coded_height = pCodecCtx->coded_height; g_stFormat.display_area.right = pCodecCtx->width; g_stFormat.display_area.left = 0; g_stFormat.display_area.bottom = pCodecCtx->height; g_stFormat.display_area.top = 0; if (pCodecCtx->codec_id == AV_CODEC_ID_H264 || pCodecCtx->codec_id == AV_CODEC_ID_HEVC) { if (pCodecCtx->codec_id == AV_CODEC_ID_H264) h264bsfc = av_bitstream_filter_init("h264_mp4toannexb"); else h264bsfc = av_bitstream_filter_init("hevc_mp4toannexb"); } return true; }
這裏面很是重要的一段代碼是網站
if (pCodecCtx->codec_id == AV_CODEC_ID_H264 || pCodecCtx->codec_id == AV_CODEC_ID_HEVC) { if (pCodecCtx->codec_id == AV_CODEC_ID_H264) h264bsfc = av_bitstream_filter_init("h264_mp4toannexb"); else h264bsfc = av_bitstream_filter_init("hevc_mp4toannexb"); }
網上有許多代碼和僞代碼都說實現了把數據源修改成FFmpeg,但我在嘗試的時候發現cuvidCreateVideoParser建立的Parser的回調函數都沒有調用。通過一番折騰,綜合英偉達網站、stackoverflow和FFmpeg源碼,才發現對H264數據要作一個處理才能把AVPacket有效的轉爲CUVIDSOURCEDATAPACKET。其中h264bsfc的定義爲AVBitStreamFilterContext* h264bsfc = NULL;this
void VideoSource::play_thread(LPVOID lpParam) { AVPacket *avpkt; avpkt = (AVPacket *)av_malloc(sizeof(AVPacket)); CUVIDSOURCEDATAPACKET cupkt; int iPkt = 0; CUresult oResult; while (av_read_frame(pFormatCtx, avpkt) >= 0){ if (bThreadExit){ break; } bStarted = true; if (avpkt->stream_index == videoindex){ cuCtxPushCurrent(g_oContext); if (avpkt && avpkt->size) { if (h264bsfc) { av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &avpkt->data, &avpkt->size, avpkt->data, avpkt->size, 0); } cupkt.payload_size = (unsigned long)avpkt->size; cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE) { cupkt.flags = CUVID_PKT_TIMESTAMP; if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den){ AVRational tb; tb.num = 1; tb.den = AV_TIME_BASE; cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb); } else cupkt.timestamp = avpkt->pts; } } else { cupkt.flags = CUVID_PKT_ENDOFSTREAM; } oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt); if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS)){ break; } iPkt++; //printf("Succeed to read avpkt %d !\n", iPkt); checkCudaErrors(cuCtxPopCurrent(NULL)); } av_free_packet(avpkt); } oSourceData_.pFrameQueue->endDecode(); bStarted = false; }
這裏FFmpeg讀取數據包後,對H264和HEVC格式,有一個重要的處理,就是前面提到的,spa
if (h264bsfc) { av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &avpkt->data, &avpkt->size, avpkt->data, avpkt->size, 0); }
這個處理的含義見雷霄華的博客http://blog.csdn.net/leixiaohua1020/article/details/39767055。.net
這樣,經過FFmpeg,CUVID就能夠對流進行處理了。我的嘗試過讀取本地文件和rtsp流。FFmpeg讀取rtsp流的方式居然只須要把文件改成rtsp流的地址就能夠,之前沒作過流式的,我還覺得會很複雜的。線程
這是在GTX 1080上把解碼進程(沒作顯示)開了20路解碼獲得的數據。20路1920X1080解碼還能到平局37fps,這顯卡也是6得不行。code
工程源碼:http://download.csdn.net/download/qq_33892166/9792997orm
源碼遇到了一個問題,沒找到緣由。代碼在GTX 1080和Tesla P4上的解碼效果很好。P4因爲驅動模式是TCC模式,因此只能解碼,不能顯示;1080上可解碼,可顯示。可是在我本身電腦上的GT940M上,即時是原生SDK在cuvidCreateDecoder的時候也老是報錯CUDA_ERROR_NO_DEVICE。驅動彷佛沒問題,試了CUDA的demo,CUDA運算也是正常的,查的資料代表GT940M應該是支持CUVID的。但願知道緣由的朋友能指教一二。blog
-----------------------------------------2017.7.7更新----------------------------------------
修改代碼中的一處內存泄漏問題:
把play_thread()中的
if (avpkt && avpkt->size) { if (h264bsfc) { av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &avpkt->data, &avpkt->size, avpkt->data, avpkt->size, 0); } cupkt.payload_size = (unsigned long)avpkt->size; cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE) { cupkt.flags = CUVID_PKT_TIMESTAMP; if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den){ AVRational tb; tb.num = 1; tb.den = AV_TIME_BASE; cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb); } else cupkt.timestamp = avpkt->pts; } } else { cupkt.flags = CUVID_PKT_ENDOFSTREAM; } oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt); if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS)){ break; } iPkt++;
爲
AVPacket new_pkt = *avpkt; if (avpkt && avpkt->size) { if (h264bsfc){ int a = av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &new_pkt.data, &new_pkt.size, avpkt->data, avpkt->size, avpkt->flags & AV_PKT_FLAG_KEY); if (a>0){ if (new_pkt.data != avpkt->data)//-added this { av_free_packet(avpkt); avpkt->data = new_pkt.data; avpkt->size = new_pkt.size; } } else if (a<0){ goto LOOP0; } *avpkt = new_pkt; } cupkt.payload_size = (unsigned long)avpkt->size; cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE) { cupkt.flags = CUVID_PKT_TIMESTAMP; if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den) { AVRational tb; tb.num = 1; tb.den = AV_TIME_BASE; cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb); } else cupkt.timestamp = avpkt->pts; } } else { cupkt.flags = CUVID_PKT_ENDOFSTREAM; } oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt); if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS)) { break; } av_free(new_pkt.data);
這個泄漏是av_bitstream_filter_filter形成的,解決辦法參考http://blog.csdn.net/lg1259156776/article/details/73283920 。
-----------------------------2017.8.30 補-----------------------------------
貌似仍是有小夥伴被內存泄漏難住了,這裏我給出我最新的讀取數據包線程函數的完整代碼,但願有所幫助
void VideoSource::play_thread(LPVOID lpParam) { AVPacket *avpkt; avpkt = (AVPacket *)av_malloc(sizeof(AVPacket)); CUVIDSOURCEDATAPACKET cupkt; CUresult oResult; while (av_read_frame(pFormatCtx, avpkt) >= 0){ LOOP0: if (bThreadExit){ break; } if (avpkt->stream_index == videoindex) { AVPacket new_pkt = *avpkt; if (avpkt && avpkt->size) { if (h264bsfc){ int a = av_bitstream_filter_filter(h264bsfc, pFormatCtx->streams[videoindex]->codec, NULL, &new_pkt.data, &new_pkt.size, avpkt->data, avpkt->size, avpkt->flags & AV_PKT_FLAG_KEY); if (a>0){ if (new_pkt.data != avpkt->data)//-added this { av_free_packet(avpkt); avpkt->data = new_pkt.data; avpkt->size = new_pkt.size; } } else if (a<0){ goto LOOP0; } *avpkt = new_pkt; } cupkt.payload_size = (unsigned long)avpkt->size; cupkt.payload = (const unsigned char*)avpkt->data; if (avpkt->pts != AV_NOPTS_VALUE) { cupkt.flags = CUVID_PKT_TIMESTAMP; if (pCodecCtx->pkt_timebase.num && pCodecCtx->pkt_timebase.den) { AVRational tb; tb.num = 1; tb.den = AV_TIME_BASE; cupkt.timestamp = av_rescale_q(avpkt->pts, pCodecCtx->pkt_timebase, tb); } else cupkt.timestamp = avpkt->pts; } } else { cupkt.flags = CUVID_PKT_ENDOFSTREAM; } oResult = cuvidParseVideoData(oSourceData_.hVideoParser, &cupkt); if ((cupkt.flags & CUVID_PKT_ENDOFSTREAM) || (oResult != CUDA_SUCCESS)) { break; } av_free(new_pkt.data); } else av_free_packet(avpkt); } oSourceData_.pFrameQueue->endDecode(); bStarted = false; if (pCodecCtx->codec_id == AV_CODEC_ID_H264 || pCodecCtx->codec_id == AV_CODEC_ID_HEVC) { av_bitstream_filter_close(h264bsfc); } }