Skip to content

FFMPEG

ffmpeg源码编译

最终的ffmpeg配置(环境Ubuntu22.04): ffmpeg

软件 版本
ffmpeg 2023-04-10在官网下的
fdk-aac 2.0.2
sdl2 2.26.5
x264 0.164.x

默认安装路径:/usr/local 参考链接

安装前置依赖项

Bash
# nasm是libx264的编译依赖
sudo apt install build-essential cmake nasm

下载编译fdk-aac

sourceforge下载fdk-aac-2.0.2.tar.gz

Bash
1
2
3
4
5
6
# 解压
tar -xvf fdk-aac-2.0.2.tar.gz
# 配置
cd fdk-aac-2.0.2 && ./configure
# 编译安装
make && sudo make install

下载编译x264

x264官网下载x264

Bash
1
2
3
4
5
6
# 解压
tar -xvf x264-master.tar.bz2
# 配置
cd x264-master && ./configure --disable-opencl --disable-asm --enable-static --enable-shared --enable-pic
# 编译安装
make -j8 && sudo make install

下载编译ffmpeg源码

ffmpeg官网下载Source Code

Bash
1
2
3
4
5
6
# 解压
tar -xvf ffmpeg-snapshot.tar.bz2
# 配置
cd ffmpeg && ./configure --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-filter=delogo --enable-debug --disable-optimizations --enable-shared --enable-pthreads
# 编译安装
make -j4 && sudo make install

问题一:安装后无ffplay

这样编译出来会发现没有ffplay,只有ffmpeg、ffprobe,是因为没有ffplay依赖的sdl,再安装sdl,SDL官网

Bash
1
2
3
4
# 配置
cd SDL2-2.26.5 && ./configure
# 编译安装
make && sudo make install

然后再重新编译安装ffmpeg

Bash
1
2
3
# 再配置中添加 --enable-sdl2
./configure ... --enable-sdl2
make -j4 && sudo make install

问题二:ffplay无法播放

报错信息:

Bash
# 很多都是no such video device, 我遇到的是没有音频设备
Could not initialize SDL - dsp: No such audio device

安装相关库(咱也不知道为什么是这俩音频库,反正安装了能播就行

Bash
sudo apt install libasound2-dev libpulse-dev

然后重新编译安装下SDL2应该就可以了。

问题三:可能ffmpeg命令失效/编写的c++程序报错找不到动态库

报错信息:

Bash
error while loading shared libraries: libavcodec.so.6

在~/.bashrc中添加环境变量

Bash
1
2
3
4
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# 如果是找不到头文件,添加下面的环境变量
export C_INCLUDE_PATH=xxx/path:$C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=xxx/path:$CPLUS_INCLUDE_PATH

ffmpeg命令

ffplay播放

Bash
1
2
3
4
5
6
7
8
# 播放h264
ffplay *.h264
# 播放aac
ffplay *.aac
# 播放pcm
ffplay -ar 48000 -ac 2 -f s16le -i *.pcm
# 播放yuv
ffplay -f rawvideo  -video_size 1280x720 -pixel_format nv12 *.yuv

推流

Bash
ffmpeg -i ${input_video} -f flv rtmp://${server}/live/${stream_name}
# -i 输入文件路径/url
# -f 强制采用某种格式 rtmp推流一般采用flv流数据
# -vcodec copy -an 只推视频,不推音频
# -re read input at native frame rate, 按照时间戳顺序推流
# -stream_loop -1 无限循环播放
# -flvflags no_duration_filesize 不要抛出duration_filesize警告
ffmpeg -i ${input_video} -c copy -f rtsp rtsp://${server}/stream
# 如果需要对视频文件进行重编码,以实现去除b帧或限定帧slice大小(构造i帧分片情况),可以使用x264opts进行重编码
ffmpeg -stream_loop -1 -re -i file.h264 -c:v libx264 -x264opts "slice-max-size=16384:bframes=0" -f rtsp rtsp://xxx

加时间戳水印

ffmpeg固定码率给MP4增加时间水印:-ac: 音频通道 -ar: 音频采样率

Bash
./ffmpeg.exe -i 720HDR30.mp4  -vf "drawtext=fontsize=100:text='%{pts\:hms}':x=25:y=25:fontcolor=red" -b 6991k -c:v libx264 -c:a copy -f mp4 output.mp4 -y

去除视频中的B帧

在不支持B帧的解码器中,如果传入了B帧会导致解码播放的视频卡顿

Bash
1
2
3
4
5
6
# -bf 0方式
ffmpeg -i *.mp4 -vcodec libx264 -bf 0 out.h264
# -x264opts "bframe=0"fangs
ffmpeg -i *.mp4 -vcodec libx264 -x264opts "bframes=0" out.h264
# -profile:v baseline方式
ffmpeg -i *.mp4 -vcodec libx264 -profile:v baseline -pix_fmt yuv420p -s 1280x720 -acodec aac -r 30 -g 30 out.mp4

查看视频中是否含有B帧

Bash
ffprobe -v quiet -show_frames -select_streams v *.mp4 | grep "pict_type=B"

设定gop值

-g参数指定gop帧数,修改gop时要重新编码即需要指定 -c:v libx264等字眼

Bash
ffmpeg -i sample.mkv -c:v libx264 -b:v 2048k -s 1920x1080 -r 30 -g 250 -c:a libfdk_aac -b:a 128k -ac 2 -ar 44100 out.mp4

修改分辨率

-vf scale=x:y

Bash
ffmpeg -i input.mp4 -vf scale=1280:720 output.mp4

ffmpeg音视频文件解码显示

C++
// 打开音视频文件,存储在AVFormatContext中
avformat_open_input(&AVFormatContext, file, nullptr, nullptr);
// 搜索流信息,信息存储在AVFormatContext->streams中
avformat_find_stream_info(AVFormatContext, nullptr);
// 打印相关信息
// av_dump_format(AVFormatContext, 0, file, 0);
// 查找第一个音频流/视频流
for (int i = 0; i < AVFormatContext->nb_streams; ++i) {
    if (AVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
        v_index = i;
    }
    if (AVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        a_index = i;
    }
}
// 获取解码器参数->获取解码器->构建解码器
AVCodecParameters = AVFormatContext->streams[a_index/v_index]->codecpar;
AVCodec = avcodec_find_decoder(AVCodecParameters->codec_id);
AVCodecContext = avcodec_alloc_context3(AVCodec);
avcodec_parameters_to_context(AVCodecContext, AVCodecParameters);
avcodec_open2(AVCodecContext, AVCodec, nullptr);

// 视频解码为yuv
AVPacket packet = (AVPacket*)av_malloc(sizeof(AVPacket));
AVFrame* raw = av_frame_alloc();
AVFrame* yuv = av_frame_alloc();
// 视频数据缓冲区
buf_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, AVCodecContext->width, AVCodecContext->height, 1);
buffer = (uint8_t*)av_malloc(buf_size);
// 设定yuv->data, yuv->linesize
// ret = av_image_fill_arrays(p_frm_yuv->data,     // dst data[]
//                            p_frm_yuv->linesize, // dst linesize[]
//                            buffer,              // src buffer
//                            AV_PIX_FMT_YUV420P,  // pixel format
//                            p_codec_ctx->width,  // width
//                            p_codec_ctx->height, // height
//                            1                    // align
// );
av_image_fill_arrays(yuv->data, yuv->linesize, buffer, AV_PIX_FMT_YUV420P, AVCodecContext->width, AVCodecContext->height, 1);

// 初始化SWS context 用于图像转换
// sws_ctx = sws_getContext(p_codec_ctx->width,   // src width
//                              p_codec_ctx->height,  // src height
//                              p_codec_ctx->pix_fmt, // src format
//                              p_codec_ctx->width,   // dst width
//                              p_codec_ctx->height,  // dst height
//                              AV_PIX_FMT_YUV420P,   // dst format
//                              SWS_BICUBIC,          // flags
//                              NULL,                 // src filter
//                              NULL,                 // dst filter
//                              NULL                  // param
//     );
SwsContext* sws_ctx = sws_getContext(p_codec_ctx->width, p_codec_ctx->height, p_codec_ctx->pix_fmt, p_codec_ctx->width, p_codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC nullptr, nullptr, nullptr);
// SDL窗口
SDL_Window* screan = SDL_CreateWindow("window name", SDL_WINDOWPOS_UNDEFINED, SDLWINDOWPOS_UNDEFINED, p_codec_ctx->width, p_codec_ctx->height, SDL_WINDOW_OPENGL)
// SDL 渲染
SDL_Renderer* sdl_renderer = SDL_CreateRenderer(screen, -1, 0);
// SDL 纹理一个texture对应一帧yuv数据
SDL_Texture* sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, p_codec_ctx->width, p_codec_ctx->height);
// 数据流向: p_fmt_ctx -> p_packet -> p_codec_ctx -> p_frm_raw
// 向解码器喂数据
av_read_frame(p_fmt_ctx, p_packet);
avcodec_send_packet(p_codec_ctx, p_packet);
av_packet_unref(p_packet);
// 接收解码器输出的数据
avcodec_receive_frame(p_codec_ctx, p_frm_raw);
// 图像转换: p_frm_raw->data --> p_frm_yuv->data.
// YUV有Y、U、V三个plane;slice是图像中一篇连续的行;stride/pitch是一行图像所占的字节数(如需要进行字节对齐)
// AVFrame->data[]:元素指向对应的plane
// AVFrame->linesize[]:元素表示对应plane中一行图像所占的字节数
sws_scale(sws_ctx,                                // sws context
         (const uint8_t* const*)p_frm_raw->data, // src slice
         p_frm_raw->linesize,                    // src stride
         0,                                      // src slice y
         p_codec_ctx->height,                    // src slice height
         p_frm_yuv->data,                        // dst planes
         p_frm_yuv->linesize                     // dst strides
);
// 使用新的YUV数据更新SDL_Rect
SDL_UpdateYUVTexture(sdl_texture,            // sdl texture
                     &sdl_rect,              // sdl rect
                     p_frm_yuv->data[0],     // y plane
                     p_frm_yuv->linesize[0], // y pitch
                     p_frm_yuv->data[1],     // u plane
                     p_frm_yuv->linesize[1], // u pitch
                     p_frm_yuv->data[2],     // v plane
                     p_frm_yuv->linesize[2]  // v pitch
);
// 使用特定颜色清空当前渲染目标
SDL_RenderClear(sdl_renderer);
// 使用部分图像数据(texture)更新当前渲染目标
SDL_RenderCopy(sdl_renderer, // sdl renderer
               sdl_texture,  // sdl texture
               NULL,         // src rect, if NULL copy texture
               &sdl_rect     // dst rect
);

// 执行渲染,更新屏幕显示
SDL_RenderPresent(sdl_renderer);