首页 > 代码库 > ffmpeg nvenc编码

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位的。

ffmpeg nvenc编码