在Ubuntu14.04版本上编译安装ffmpeg3.4.8,开启NVIDIA硬件加速功能。
一、安装依赖库
sudo apt-get install libtool automake autoconf nasm yasm //nasm yasm注意版本
sudo apt-get install libx264-dev
sudo apt-get install libx265-dev
sudo apt-get install libmp3lame-dev
sudo apt-get install libvpx-dev
sudo apt-get install libfaac-dev
二、安装ffnvcodec
git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers.git
cd nv-codec-headers
make
sudo make install
三、安装NVIDIA驱动
直接使用apt安装方便,在官方网站下载驱动未安装成功
1.卸载系统里的Nvidia低版本显卡驱动
sudo apt-get purge nvidia*
2.把显卡驱动加入PPA
sudo add-apt-repository ppa:graphics-drivers
sudo apt-get update
3.查找显卡驱动最新的版本号
查找并安装最新驱动
sudo apt-cache search nvidia
sudo apt-get install nvidia-430
四、安装CUDA
CUDA是Nvidia出的一个GPU计算库,让程序员可以驱动Nvidia显卡的GPU进行各种工作,其中就包含了视频的编解码。
1.下载https://developer.download.nvidia.cn/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_8.0.44-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1604_8.0.44-1_amd64.deb
sudo apt-get update
sudo apt-get install cuda
2.检查驱动和CUDA安装是否成功
nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 430.64 Driver Version: 430.64 CUDA Version: 10.1 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GeForce GTX 106... Off | 00000000:01:00.0 On | N/A |
| 42% 25C P8 6W / 120W | 62MiB / 6077MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| 0 1311 G /usr/lib/xorg/Xorg 59MiB |
+-----------------------------------------------------------------------------+
五、编译ffmpeg
./configure --enable-gpl --enable-version3 --enable-nonfree --enable-shared --enable-ffmpeg --enable-ffplay --enable-ffprobe --enable-ffserver --enable-libx264 --enable-nvenc --enable-cuda --enable-cuvid --enable-libnpp --extra-cflags=-I/usr/local/cuda/include --extra-ldflags=-L/usr/local/cuda/lib64
make
sudo make install
检查:
ffmpeg -codecs | grep nv
DEV.LS h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_cuvid ) (encoders: libx264 libx264rgb h264_nvenc nvenc nvenc_h264 )
DEV.L. hevc H.265 / HEVC (High Efficiency Video Coding) (decoders: hevc hevc_cuvid ) (encoders: nvenc_hevc hevc_nvenc )
测试:
ffmpeg -i aidedaijia.mkv -c:v h264_nvenc -c:a aac output.mp4
ffmpeg -hwaccel cuvid -i output.mp4 output.yuv
六、修改部分
1.突破NVIDIA显卡NVENC并发Session数目限制
具体查看https://developer.nvidia.com/video-encode-decode-gpu-support-matrix#Encoder
我使用的是gtx1060显卡,最大只能并发2路编码,最后看到老雷blog突破NVIDIA NVENC并发Session数目限制,发现是驱动里面进行了限制。但老雷是windows下进行了修改,Linux下修改方法在githu中有(找了很久),而且可以针对很多驱动程序版本都做了匹配(牛!)。地址在:https://github.com/keylase/nvidia-patch
git clone https://github.com/keylase/nvidia-patch.git
sudo bash ./patch.sh (修改驱动)
sudo bash ./patch.sh -r (恢复原驱动)
2.关于nvenc编码输出帧的问题
使用nvenc和h264_nvenc编码的时候,每帧的前面都添加了一个SEI帧,在使用RTP传输的时候,对方接收到的编码出错,于是直接在ffmpeg源码进行了修改。
在ffmpeg-3.4.8/libavcodec/nvenc.c文件中修改:
if (IS_CBR(cc->rcParams.rateControlMode)) {
h264->outputBufferingPeriodSEI = 1;//不知道干啥用的,修改了没有发现编码有变化
}
h264->outputPictureTimingSEI = 1;//重要是修改这里,将1修改为0,每个帧前就不再输出SEI帧了。
七、测试编码程序
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <stdio.h>
/*
* Video encoding example
*/
#define H264_START_CODE 0x000001
uint32_t h264_find_next_start_code (uint8_t *pBuf,
uint32_t bufLen)
{
uint32_t val;
uint32_t offset;
offset = 0;
if (pBuf[0] == 0 && pBuf[1] == 0 && pBuf[2] == 0 && pBuf[3] == 1) {
pBuf += 4;
offset = 4;
} else if (pBuf[0] == 0 && pBuf[1] == 0 && pBuf[2] == 1) {
pBuf += 3;
offset = 3;
}
val = 0xffffffff;
while (offset < bufLen - 3) {
val <<= 8;
val |= *pBuf++;
offset++;
if (val == H264_START_CODE) {
return offset - 4;
}
if ((val & 0x00ffffff) == H264_START_CODE) {
return offset - 3;
}
}
return 0;
}
static void video_encode_example(const char *filename)
{
AVCodec *codec;
AVCodecContext *c = NULL;
int i, ret, x, y, got_output,k;
AVFrame *frame;
AVPacket pkt;
uint8_t endcode[] = { 0, 0, 1, 0xb7 };
// av_log_set_level(64);
printf("Encode video file %s\n", filename);
/* find the video encoder */
// codec = avcodec_find_encoder_by_name("libx264");
codec = avcodec_find_encoder_by_name("nvenc");//nvenc nvenc_h264 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);
}
c->bit_rate = 4000*1024;
c->width = 1920;
c->height = 1080;
c->time_base.num = 1;
c->time_base.den = 60;
c->gop_size = 10;
c->pix_fmt = AV_PIX_FMT_YUV420P;//AV_PIX_FMT_CUDA;
c->max_b_frames = 0;
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
AVDictionary *param = 0;
//H.264
if (codec->id == AV_CODEC_ID_H264) {
// av_dict_set(¶m, "preset", "medium", 0);
// av_dict_set(¶m, "tune", "zerolatency", 0);
// av_dict_set(¶m, "rc", "cbr", 0);
}
//H.265
if (codec->id == AV_CODEC_ID_H265 || codec->id == AV_CODEC_ID_HEVC){
//av_dict_set(¶m, "x265-params", "qp=20", 0);
av_dict_set(¶m, "x265-params", "crf=25", 0);
av_dict_set(¶m, "preset", "fast", 0);
av_dict_set(¶m, "tune", "zero-latency", 0);
}
/* open it */
if (avcodec_open2(c, codec, ¶m) < 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 (i=500/25) second of video */
for (i = 0; i < 60*10; 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(pkt.flags & AV_PKT_FLAG_KEY){
printf("@-\n");//输出的是关键帧
for (k=0;k< c->extradata_size;k++){
printf("%02x ",c->extradata[k]);
}
printf("\n");
int offset=0;
offset=h264_find_next_start_code(c->extradata,c->extradata_size);
printf("offset=%d\n",offset);
int spslen= offset;
int ppslen= c->extradata_size -offset;
for (k=0;k<spslen;k++){
printf("%02x ",c->extradata[k]);
}
printf("\n");
for (k=0;k<ppslen;k++){
printf("%02x ",c->extradata[k+spslen]);
}
printf("\n");
}
printf("[%d] ",i);
if (got_output) {
for (k=0;k<46;k++){
printf("%02x ",pkt.data[k]);
}
printf(" size=%d\n",pkt.size);
//printf("\n1: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("2: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);
}
int main(int argc, char **argv)
{
/* register all the codecs */
avcodec_register_all();
video_encode_example("test.h264");
return 0;
}
编译
gcc ./encode.c -o encode -lavcodec -lavutil