ffmpeg nvenc編碼


    花時間研究了一些ffmpeg的nvenc,本來想我已經有了cuvid,然后又搞出來了nvenc,應該可以做個全套的英偉達的轉碼了,沒想到ffmpeg官網下載的動態庫沒有cuvid,windows上編譯cuvid又老是出錯,憂了個傷。

1.nvenc編碼

    h264_nvenc是很容易調出來的,把編碼器ffmpeg源碼自帶的例子的編碼器換成h264_nvenc就行了。可是hevc_nvenc就花了我好多時間,感覺調試技術還是差了好多。

#include "stdafx.h"

/*
* Video encoding example
*/
static void video_encode_example(const char *filename)
{
    AVCodec *codec;
    AVCodecContext *c = NULL;
    int i, ret, x, y, got_output;
    AVFrame *frame;
    AVPacket pkt;
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };

    av_log_set_level(64);

    //AVBufferRef *device_ref = NULL;
    //AVBufferRef *hw_frames_ctx = NULL;
    //hw_frames_ctx = (AVBufferRef *)av_mallocz(sizeof(AVBufferRef));
    //if (!hw_frames_ctx) {
    //    ret = AVERROR(ENOMEM);
    //    return ;
    //}

    //ret = av_hwdevice_ctx_create(&device_ref, AV_HWDEVICE_TYPE_CUDA,"CUDA", NULL, 0);
    //if (ret < 0)
    //    return;

    //hw_frames_ctx = av_hwframe_ctx_alloc(device_ref);
    //if (!hw_frames_ctx) {
    //    av_log(NULL, AV_LOG_ERROR, "av_hwframe_ctx_alloc failed\n");
    //    ret = AVERROR(ENOMEM);
    //    return;
    //}
    //av_buffer_unref(&device_ref);

    //c->hw_frames_ctx = av_buffer_ref(hw_frames_ctx);
    //if (!hw_frames_ctx) {
    //    av_log(NULL, AV_LOG_ERROR, "av_buffer_ref failed\n");
    //    ret = AVERROR(ENOMEM);
    //    return;
    //}

    printf("Encode video file %s\n", filename);

    /* find the video encoder */
    codec = avcodec_find_encoder_by_name("hevc_nvenc");
    //codec = avcodec_find_encoder(AV_CODEC_ID_H265);
    //codec = avcodec_find_encoder_by_name("h264_nvenc");
    //codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    /* put sample parameters */
    c->bit_rate = 400000;
    /* resolution must be a multiple of two */
    c->width = 352;
    c->height = 288;
    /* frames per second */
    c->time_base.num = 1;
    c->time_base.den = 25;
    /* emit one intra frame every ten frames
    * check frame pict_type before passing frame
    * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
    * then gop_size is ignored and the output of encoder
    * will always be I frame irrespective to gop_size
    */
    c->gop_size = 10;
    c->max_b_frames = 1;
    c->pix_fmt = AV_PIX_FMT_YUV420P;//AV_PIX_FMT_CUDA;
    c->max_b_frames = 0;

    AVDictionary *param = 0;
    //H.264
    if (codec->id == AV_CODEC_ID_H264) {
        av_dict_set(&param, "preset", "medium", 0);
        av_dict_set(&param, "tune", "zerolatency", 0);
    }
    //H.265
    if (codec->id == AV_CODEC_ID_H265 || codec->id == AV_CODEC_ID_HEVC){
        //av_dict_set(&param, "x265-params", "qp=20", 0);
        av_dict_set(&param, "x265-params", "crf=25", 0);
        av_dict_set(&param, "preset", "fast", 0);
        av_dict_set(&param, "tune", "zero-latency", 0);
    }

    /* open it */
    if (avcodec_open2(c, codec, &param) < 0) {
        fprintf(stderr, "Could not open codec\n");
        system("pause");
        exit(1);
    }

    FILE *f;
    f = fopen(filename, "wb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame->format = c->pix_fmt;
    frame->width = c->width;
    frame->height = c->height;

    /* the image can be allocated by any means and av_image_alloc() is
    * just the most convenient way if av_malloc() is to be used */
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height,
        c->pix_fmt, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate raw picture buffer\n");
        exit(1);
    }

    /* encode 1 second of video */
    for (i = 0; i < 500; i++) {
        av_init_packet(&pkt);
        pkt.data = NULL;    // packet data will be allocated by the encoder
        pkt.size = 0;

        fflush(stdout);
        /* prepare a dummy image */
        /* Y */
        for (y = 0; y < c->height; y++) {
            for (x = 0; x < c->width; x++) {
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        /* Cb and Cr */
        for (y = 0; y < c->height / 2; y++) {
            for (x = 0; x < c->width / 2; x++) {
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        frame->pts = i;

        /* encode the image */
        ret = avcodec_encode_video2(c, &pkt, frame, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }

        if (got_output) {
            printf("Write frame %3d (size=%5d)\n", i, pkt.size);
            fwrite(pkt.data, 1, pkt.size, f);
            av_packet_unref(&pkt);
        }
    }

    /* get the delayed frames */
    for (got_output = 1; got_output; i++) {
        fflush(stdout);

        ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }

        if (got_output) {
            printf("Write frame %3d (size=%5d)\n", i, pkt.size);
            fwrite(pkt.data, 1, pkt.size, f);
            av_packet_unref(&pkt);
        }
    }

    /* add sequence end code to have a real MPEG file */
    fwrite(endcode, 1, sizeof(endcode), f);
    fclose(f);

    avcodec_close(c);
    av_free(c);
    av_freep(&frame->data[0]);
    av_frame_free(&frame);
    printf("\n");
}

int main(int argc, char **argv)
{
    /* register all the codecs */
    avcodec_register_all();

    avcodec_register_all();

    avdevice_register_all();

    avfilter_register_all();
    av_register_all();
    avformat_network_init();

    video_encode_example("test.hevc");

    system("pause");

    return 0;
}

代碼中av_log_set_level(64);可以幫助輸出中間信息,參數可以自行設置,參數為越大,能輸出的信息等級越多,我的問題就是通過這個函數知道的,然后到源碼中找對應處才最終解決。編碼器為hevc_nvenc時max_b_frames必須為0,即代碼中的 c->max_b_frames = 0;另外c->pix_fmt = AV_PIX_FMT_YUV420P;//AV_PIX_FMT_CUDA;這行代碼需要注意,設置為AV_PIX_FMT_YUV420P意味着數據是從內存讀取的,設置為AV_PIX_FMT_CUDA意味着數據在顯存中,AV_PIX_FMT_CUDA與cuvid是一起的,只有編出來的ffmpeg支持cuvid時AV_PIX_FMT_CUDA才有效。

2.用nvenc做轉碼

    由於還沒有編出支持cuvid的ffmpeg,所以解碼這里就先不用cuvid了,用CPU來解碼。其實這樣有一個好處,就是對格式的要求低,cuvid對格式的輸入是有要求的,用這種方法所有用ffmpeg解碼后的數據都可以用nvenc來編碼,缺點當然是這樣比較慢了。

#include "stdafx.h"

#include <stdio.h>
#include <io.h>

/*
* Video encoding example
*/
static void video_encode_example(const char *filename)
{
    AVCodec *codec;
    AVCodecContext *c = NULL;
    int i, ret, x, y, got_output;
    AVPacket pkt;
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };

    av_log_set_level(64);

    AVFormatContext    *pFormatCtx;
    int                videoindex;
    AVCodecContext    *pCodecCtx;
    AVCodec            *pCodec;
    AVFrame    *pFrame, *pFrameYUV;
    uint8_t *out_buffer;

    char filepath[] = "H:\\nvenc\\燦爛人生1280.rmvb";

    av_register_all();
    avformat_network_init();
    pFormatCtx = avformat_alloc_context();

    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0){
        printf("Couldn't open input stream.\n");
        return ;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL)<0){
        printf("Couldn't find stream information.\n");
        return ;
    }
    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 ;
    }

    pCodecCtx = pFormatCtx->streams[videoindex]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL){
        printf("Codec not found.\n");
        return ;
    }
    if (avcodec_open2(pCodecCtx, pCodec, NULL)<0){
        printf("Could not open codec.\n");
        return ;
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();
    out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

    //Output Info-----------------------------
    printf("--------------- File Information ----------------\n");
    av_dump_format(pFormatCtx, 0, filepath, 0);
    printf("-------------------------------------------------\n");
    struct SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
        pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);


    printf("Encode video file %s\n", filename);

    /* find the video encoder */
    codec = avcodec_find_encoder_by_name("hevc_nvenc");
    //codec = avcodec_find_encoder(AV_CODEC_ID_H265);
    //codec = avcodec_find_encoder_by_name("h264_nvenc");
    //codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    /* put sample parameters */
    c->bit_rate = pCodecCtx->bit_rate;
    /* resolution must be a multiple of two */
    c->width = pCodecCtx->width;
    c->height = pCodecCtx->height;
    c->time_base = pCodecCtx->time_base;
    c->gop_size = pCodecCtx->gop_size;

    c->pix_fmt = AV_PIX_FMT_YUV420P;
    c->max_b_frames = 0;

    AVDictionary *param = 0;
    //H.264
    if (codec->id == AV_CODEC_ID_H264) {
        av_dict_set(&param, "preset", "medium", 0);
        av_dict_set(&param, "tune", "zerolatency", 0);
    }
    //H.265
    if (codec->id == AV_CODEC_ID_H265 || codec->id == AV_CODEC_ID_HEVC){
        //av_dict_set(&param, "x265-params", "qp=20", 0);
        av_dict_set(&param, "x265-params", "crf=25", 0);
        av_dict_set(&param, "preset", "fast", 0);
        av_dict_set(&param, "tune", "zero-latency", 0);
    }

    /* open it */
    if (avcodec_open2(c, codec, &param) < 0) {
        fprintf(stderr, "Could not open codec\n");
        system("pause");
        exit(1);
    }

    FILE *f;
    f = fopen(filename, "wb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    AVPacket *packet;
    packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    int got_picture;
    int iCount = 0;
    int64_t iStart = av_gettime();
    while (av_read_frame(pFormatCtx, packet) >= 0){
        if (packet->stream_index == videoindex){
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
            if (ret < 0){
                printf("Decode Error.\n");
                return ;
            }
            if (got_picture){
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
                    pFrameYUV->data, pFrameYUV->linesize);

                av_init_packet(&pkt);
                pkt.data = NULL;    // packet data will be allocated by the encoder
                pkt.size = 0;

                pFrameYUV->width = c->width;
                pFrameYUV->height = c->height;
                pFrameYUV->format = c->pix_fmt;

                /* encode the image */
                ret = avcodec_encode_video2(c, &pkt, pFrameYUV, &got_output);
                if (ret < 0) {
                    fprintf(stderr, "Error encoding frame\n");
                    exit(1);
                }

                if (got_output) {

                    iCount++;
                    fwrite(pkt.data, 1, pkt.size, f);
                    av_packet_unref(&pkt);

                    if (iCount % 1000 == 0)
                    {
                        printf("1000幀用時:%d   平均每秒 %f 幀 \n", (av_gettime() - iStart)/100000, (double)1000 * 1000000 / (av_gettime() - iStart));
                        printf("Write frame %3d (size=%5d)\n", i, pkt.size);

                        int fd = _fileno(f); //獲取文件描述符
                        _commit(fd);

                        iStart = av_gettime();
                    }

                    
                }

            }
        }
        av_free_packet(packet);
    }

    /* get the delayed frames */
    for (got_output = 1; got_output; i++) {
        fflush(stdout);

        ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }

        if (got_output) {
            printf("Write frame %3d (size=%5d)\n", i, pkt.size);
            fwrite(pkt.data, 1, pkt.size, f);
            av_packet_unref(&pkt);
        }
    }

    /* add sequence end code to have a real MPEG file */
    fwrite(endcode, 1, sizeof(endcode), f);
    fclose(f);

    avcodec_close(c);
    av_free(c);
    printf("\n");
}

int main(int argc, char **argv)
{
    /* register all the codecs */
    avcodec_register_all();

    avcodec_register_all();

    avdevice_register_all();

    avfilter_register_all();
    av_register_all();
    avformat_network_init();

    video_encode_example("H:\\nvenc\\test.hevc");

    system("pause");

    return 0;
}

上面用nvenc編出來的並沒有封裝成文件。h264格式potplayer還可以直接播放,hevc格式就不行了。hevc必須先封裝,比如封裝成mp4文件,然后才能播放。如何封裝的例子網上比較多,我這里給個ffmpeg命令行的示例:

ffmpeg -i f:\25國.265 -c:v copy -f mp4 f:\25國.mp4

 

最后,nvenc對顯卡的要求好像比較高,注意查看自己的顯卡是否支持nvenc。

工程源碼:http://download.csdn.net/download/qq_33892166/9840113

源碼的ffmpg是64位的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM