英偉達CUVID硬解,並經過FFmpeg讀取文件

      雖然FFmpeg自己有cuvid硬解,可是找不到什麼好的資料,英偉達的SDK比較容易懂,參考FFmpeg源碼,將NVIDIA VIDEO CODEC SDK的數據獲取改成FFmpeg獲取,彌補原生SDK不能以流做爲數據源的不足。所用SDK版本爲Video_Codec_SDK_7.1.9,英偉達官網可下載。ide

1.修改數據源

    首先是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

2.AVPacket轉CUVIDSOURCEDATAPACKET,並交給cuvidParseVideoData

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流的地址就能夠,之前沒作過流式的,我還覺得會很複雜的。線程

3.一點數據

20路

    這是在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);
    }
}
相關文章
相關標籤/搜索