FFmpeg自学入门笔记

FFmpeg是一种完整的跨平台解决方案,用于录制,转换和流式传输音频和视频。

FFMPEG分为3个版本:Static,Shared,Dev。
前两个版本可以直接在命令行中使用,他们的区别在于:Static里面只有3个应用程序:ffmpeg.exe,ffplay.exe,ffprobe.exe,每个exe的体积都很大,相关的Dll已经被编译到exe里面去了。Shared里面除了3个应用程序:ffmpeg.exe,ffplay.exe,ffprobe.exe之外,还有一些Dll,比如说avcodec-54.dll之类的。Shared里面的exe体积很小,他们在运行的时候,到相应的Dll中调用功能。
Dev版本是用于开发的,里面包含了库文件xxx.lib以及头文件xxx.h,这个版本不包含exe文件。
打开系统命令行接面,切换到ffmpeg所在的目录,就可以使用这3个应用程序了。

ffmpeg是用于转码的应用程序。(ffmpeg详细的使用说明(英文):http://ffmpeg.org/ffmpeg.html)
具体的使用方法可以参考:雷霄骅 ffmpeg参数中文详细解释(https://blog.csdn.net/leixiaohua1020/article/details/12751349)

ffplay是用于播放的应用程序。(详细的使用说明(英文):http://ffmpeg.org/ffplay.html)

ffprobe是用于查看文件格式的应用程序。(详细的使用说明(英文):http://ffmpeg.org/ffprobe.html)

FFmpeg为开发人员提供的库:

libavutil是一个包含简化编程功能的库,包括随机数生成器,数据结构,数学例程,核心多媒体实用程序等等。
libavcodec是一个包含用于音频/视频编解码器的解码器和编码器的库。
libavformat是一个包含多媒体容器格式的解复用器和复用器的库。
libavdevice是一个包含输入和输出设备的库,用于从许多常见的多媒体输入/输出软件框架中获取和呈现,包括Video4Linux,Video4Linux2,VfW和ALSA。
libavfilter是一个包含媒体过滤器的库。
libswscale是一个执行高度优化的图像缩放和色彩空间/像素格式转换操作的库。
libswresample是一个执行高度优化的音频重采样,重新矩阵化和样本格式转换操作的库。

一、视频编码基础

视音频基础知识:(自行百度)
1.视频播放器原理
2.封装格式(MP4,RMVB,TS,FLV,AVI)
这里补充一下mp4封装学习记录:
在这里插入图片描述
3.视频编码数据(H.264,MPEG2,VC-1)
在这里插入图片描述
4.音频编码数据(AAC,MP3,AC-3)
5.视频像素数据(YUV420P,RGB)
6.音频采样数据(PCM)

二、FFmpeg_4.1编译安装

# 依赖项
$ sudo apt-get install libgtk2.0-dev  libjpeg.dev libjasper-dev yasm
# 配置
$ sudo ./configure --enable-shared --disable-static
# 如果想要在 FFMPEG 中编译 QSV 硬件编码器,则需要使用下面命令进行配置:
$ sudo ./configure --enable-libmfx --enable-encoder=h264_qsv --enable-decoder=h264_qsv --enable-shared --disable-static
# 编译
3.sudo make -j12
# 安装
4.sudo make install
# 如果开启了QSV,则可以使用此命令验证:
$ ffmpeg -codecs | grep qsv
ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers
  built with gcc 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.10) 20160609
  configuration: --enable-libmfx --enable-encoder=h264_qsv --enable-decoder=h264_qsv --enable-shared --disable-static
  libavutil      56. 22.100 / 56. 22.100
  libavcodec     58. 35.100 / 58. 35.100
  libavformat    58. 20.100 / 58. 20.100
  libavdevice    58.  5.100 / 58.  5.100
  libavfilter     7. 40.101 /  7. 40.101
  libswscale      5.  3.100 /  5.  3.100
  libswresample   3.  3.100 /  3.  3.100
 DEV.LS h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_v4l2m2m h264_qsv ) (encoders: h264_qsv h264_v4l2m2m h264_vaapi )
 DEV.L. hevc                 H.265 / HEVC (High Efficiency Video Coding) (decoders: hevc hevc_qsv ) (encoders: hevc_qsv hevc_vaapi )
 DEVIL. mjpeg                Motion JPEG (encoders: mjpeg mjpeg_qsv mjpeg_vaapi )
 DEV.L. mpeg2video           MPEG-2 video (decoders: mpeg2video mpegvideo mpeg2_v4l2m2m mpeg2_qsv ) (encoders: mpeg2video mpeg2_qsv mpeg2_vaapi )
 D.V.L. vc1                  SMPTE VC-1 (decoders: vc1 vc1_qsv vc1_v4l2m2m )
 DEV.L. vp8                  On2 VP8 (decoders: vp8 vp8_v4l2m2m vp8_qsv ) (encoders: vp8_v4l2m2m vp8_vaapi )

三、FFmpeg命令行

ffmpeg工具:
$ ffmpeg -i input.mp4 output.avi
ffplay工具:
$ ffplay output.avi
ffmpeg-修改分辨率:
# 法一:(缺点:如果分辨率的比例跟原视频的比例不一样,会导致视屏变形)
$ ffmpeg -i 1.mp4 -strict -2 -s 640x480 4.mp4
# 法二:(-1表示按照比例缩放,可保证视屏不会变形)
$ ffmpeg -i 1.mp4 -strict -2 -vf scale=-1:480 4.mp4

要将输出文件的视频比特率设置为64 kbit / s:

$ ffmpeg -i input.avi -b:v 64k -bufsize 64k output.avi

要强制输出文件的帧速率为24 fps:

$ ffmpeg -i input.avi -r 24 output.avi

要强制输入文件的帧速率(仅对原始格式有效)为1 fps,输出文件的帧速率为24 fps:

$ ffmpeg -r 1 -i input.m2v -r 24 output.avi
ffmpeg每个输出的转码过程可以通过下图描述:
 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                       _________
                                      |         |
                                      | decoded |
                                      | frames  |
                                      |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|

ffmpeg调用libavformat库来读取输入文件并从中获取包含编码数据的数据包,然后将编码的数据包传递给解码器,解码器产生未压缩的帧(原始视频/ PCM音频/ …),可以通过过滤进一步处理。在过滤之后,帧被传递到编码器,编码器对它们进行编码并输出编码的分组。最后,这些传递给复用器,复用器将编码的数据包写入输出文件。

扫描二维码关注公众号,回复: 6229159 查看本文章

四、FFmpeg代码-部分函数

FFmpeg 解码流程所需要调用的 API 依次为:

av_register_all();
avformat_open_input(); //打开视频输入
av_find_stream_info(); //判断有无stream通道
av_find_best_stream(); //穷举所有的流,查找其中种类为CODEC_TYPE_VIDEO
avcodec_find_decoder(); //获取解码器
avcodec_alloc_context3(); //解码器内存分配
avcodec_parameters_to_context(); //填充解码参数
avcodec_open2(); //打开解码器
av_image_get_buffer_size(); //?
av_image_fill_arrays(); //?
while(av_read_frame()) {
    avcodec_send_packet(); //送入解码器,数据包解码
    avcodec_receive_frame(); //提取解码后的数据
    sws_getCachedContext(); //图像格式转换,涉及到图像缩放和格式转换算法。 //可取代sws_getContext(),因为sws_getCachedContext()会先判断是否需要再申请上下文内存空间。
    sws_scale(); //开始转换(sws_scale库可以在一个函数里面同时实现:1.图像色彩空间转换;2.分辨率缩放;3.前后图像滤波处理。)
}

H264视频流不解码,直接输出到另存文件的流程:

avcodec_register_all();
av_register_all();
avformat_network_init();
avformat_open_input(); //打开视频输入
av_find_stream_info(); //判断有无stream通道
avformat_alloc_output_context2(); //初始化用于输出的AVFormatContext结构体
avformat_new_stream(); //在输出对象中开启一路视频流通道
avcodec_parameters_copy(); //拷贝输入参数到输出参数中
avio_open(); //打开输出文件
av_dict_set(); //设置输出配置,可省略
avformat_write_header(); //写文件头
av_init_packet();
while() {
    av_read_frame(); //取出一帧
    av_interleaved_write_frame(); //写视频帧到文件
}
avformat_close_input(&i_fmt_ctx);
av_write_trailer(o_fmt_ctx); //写文件尾
avcodec_close(o_fmt_ctx->streams[0]->codec);
av_freep(&o_fmt_ctx->streams[0]->codec);
av_freep(&o_fmt_ctx->streams[0]);
avio_close(o_fmt_ctx->pb);
av_free(o_fmt_ctx);
av_packet_alloc()

使用av_packet_alloc来创建一个AVPacket的实例,但该函数并不会为数据分配空间,其指向数据域的指针为NULL。
通常调用av_read_frame将流中的数据读取到AVPacket中。
所以在每次循环的结束不能忘记调用av_packet_unref减少数据域的引用技术,当引用技术减为0时,会自动释放数据域所占用的空间。在循环结束后,调用av_packet_free来释放AVPacket本身所占用的空间。

av_packet_free()

首先将AVPacket指向的数据域的引用技术减1(数据域的引用技术减为0时会自动释放)
接着,释放为AVPacket分配的空间。
av_packet_free替代已被废弃的av_free_packet。

av_packet_unref()
void av_packet_unref(AVPacket *pkt)
{
    av_packet_free_side_data(pkt);
    av_buffer_unref(&pkt->buf);
    av_init_packet(pkt);
    pkt->data = NULL;
    pkt->size = 0;
}
av_init_packet()
void av_init_packet(AVPacket *pkt)
{
    pkt->pts                  = AV_NOPTS_VALUE;
    pkt->dts                  = AV_NOPTS_VALUE;
    pkt->pos                  = -1;
    pkt->duration             = 0;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGS
    pkt->convergence_duration = 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    pkt->flags                = 0;
    pkt->stream_index         = 0;
    pkt->buf                  = NULL;
    pkt->side_data            = NULL;
    pkt->side_data_elems      = 0;
}
FFmpeg的libswscale的示例

参考:雷霄骅:https://blog.csdn.net/leixiaohua1020/article/details/42134965
FFmpeg支持多种像素拉伸的方式,定义位于libswscale\swscale.h中。

#define SWS_FAST_BILINEAR     1 //性能和速度之间有一个比好好的平衡
#define SWS_BILINEAR          2 //(Bilinear interpolation,双线性插值):4个点确定插值的点。
#define SWS_BICUBIC           4 //(Bicubic interpolation,双三次插值)性能比较好:16个点确定插值的点。
#define SWS_X                 8
#define SWS_POINT          0x10 //(邻域插值)效果比较差:根据距离它最近的样点的值取得自己的值。
#define SWS_AREA           0x20
#define SWS_BICUBLIN       0x40
#define SWS_GAUSS          0x80
#define SWS_SINC          0x100
#define SWS_LANCZOS       0x200
#define SWS_SPLINE        0x400

END、测试代码

ffmpeg保存rtsp视频流小视频:

//
// Created by toson on 19-4-22.
//
​
#ifdef __cplusplus
extern "C" {
#endif
​
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
​
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
​
#ifdef __cplusplus
}
#endif
​
​
#include "unistd.h"
#include <iostream>
#include "thread"
#include "istream"
#include "chrono"
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::steady_clock;
using std::chrono::duration_cast;
​
using namespace std;
​
bool bStop = false;
​
static signed rtsp2mp4()
{
    int err = 0;
​
    AVFormatContext *i_fmt_ctx = nullptr;
    AVStream *i_video_stream = nullptr;
​
    AVFormatContext *o_fmt_ctx = nullptr;
    AVStream *o_video_stream = nullptr;
​
    avcodec_register_all();
    av_register_all();
    avformat_network_init();
​
    // should set to NULL so that avformat_open_input() allocate a new one
    i_fmt_ctx =  nullptr;//avformat_alloc_context();
    char rtspUrl[] = "rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=0";
    const char* filename = "/home/toson/ffmpeg_test_videos/ffmpeg_t1_o8.mp4";
​
    AVDictionary *dict = nullptr;
//    av_dict_set(&dict, "rtsp_transport", "tcp", 0);
//    av_dict_set(&dict, "stimeout", "3000000", 0); //设置超时时间3s,否则网络不通时会阻塞
//    av_dict_set(&dict, "buffer_size", "1024000", 0);
//    av_dict_set(&dict, "max_delay", "50000", 0);
​
    if (avformat_open_input(&i_fmt_ctx, rtspUrl, nullptr, nullptr)!=0)
    {
        fprintf(stderr, "could not open input file\n");
        return -1;
    }
​
    if (avformat_find_stream_info(i_fmt_ctx, nullptr)<0)
    {
        fprintf(stderr, "could not find stream info\n");
        return -1;
    }
​
    //av_dump_format(i_fmt_ctx, 0, argv[1], 0);
​
    // find first video stream
    for (unsigned i=0; i<i_fmt_ctx->nb_streams; i++)
    {
        if (i_fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            i_video_stream = i_fmt_ctx->streams[i];
            break;
        }
    }
    if (i_video_stream == nullptr)
    {
        fprintf(stderr, "didn't find any video stream\n");
        return -1;
    }
​
    // 初始化一个用于输出的AVFormatContext结构体
    err = avformat_alloc_output_context2(&o_fmt_ctx, nullptr, nullptr, filename);
    std::cout << err << " = avformat_alloc_output_context2" << std::endl;
​
    // since all input files are supposed to be identical (framerate, dimension, color format, ...)
    // we can safely set output codec values from first input file
    o_video_stream = avformat_new_stream(o_fmt_ctx, nullptr);
    o_video_stream->id = 0;
    o_video_stream->codecpar->codec_tag = 0;
    avcodec_parameters_copy(o_video_stream->codecpar, (*(i_fmt_ctx->streams))->codecpar);
    // 或者使用下面方式
//                    AVCodecContext *c = o_video_stream->codec;
//                    c->bit_rate = 400000;
//                    c->codec_id = i_video_stream->codec->codec_id;
//                    c->codec_type = i_video_stream->codec->codec_type;
//                    c->time_base.num = i_video_stream->time_base.num;
//                    c->time_base.den = i_video_stream->time_base.den;
//                    fprintf(stderr, "time_base.num = %d time_base.den = %d\n", c->time_base.num, c->time_base.den);
//                    c->width = i_video_stream->codec->width;
//                    c->height = i_video_stream->codec->height;
//                    c->pix_fmt = i_video_stream->codec->pix_fmt;
//                    printf("%d %d %d", c->width, c->height, c->pix_fmt);
//                    c->flags = i_video_stream->codec->flags;
//                    c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
//                    c->me_range = i_video_stream->codec->me_range;
//                    c->max_qdiff = i_video_stream->codec->max_qdiff;
//                    c->qmin = i_video_stream->codec->qmin;
//                    c->qmax = i_video_stream->codec->qmax;
//                    c->qcompress = i_video_stream->codec->qcompress;
    avio_open(&o_fmt_ctx->pb, filename, AVIO_FLAG_WRITE);
    av_dict_set(&dict, "movflags", "faststart", 0);// 设置 moov 前置
    err = avformat_write_header(o_fmt_ctx, &dict);
    if(err) {
        fprintf(stderr, "%d = avformat_write_header\n", err);
    }
​
    AVPacket i_pkt;
    av_init_packet(&i_pkt);
    bool first = true;
​
    // 警告:Application provided invalid, non monotonically increasing dts to muxer in stream 0: 52200 >= 43200
    // 百度查到的说法是:发现源文件的Video的duration 和Audio的duration不同,所以声音和图像无法同步。
    // 我丢弃了这些帧:
    av_read_frame(i_fmt_ctx, &i_pkt);
​
    int count = 0;
    while (!bStop)
    {
        steady_clock::time_point idtime = steady_clock::now();
        av_init_packet(&i_pkt);
        av_packet_unref(&i_pkt);
​
        if (av_read_frame(i_fmt_ctx, &i_pkt) <0 )
            break;
        std::cout << "av_read_frame cost: "
                  << duration_cast<chrono::milliseconds>(steady_clock::now() - idtime).count()
                  << "ms" << std::endl;
​
        // 判断是否关键帧。(首帧务必要从关键帧开始。)
        if (first){
            if (i_pkt.flags & AV_PKT_FLAG_KEY) {
                o_video_stream->start_time = 0;
                o_video_stream->duration = 0;
                o_video_stream->first_dts = i_pkt.dts;
                o_video_stream->cur_dts = 0;
                o_video_stream->last_IP_pts = 0;
            } else {
                continue;
            }
        }
        first = false;
​
        // pts and dts should increase monotonically
        // pts should be >= dts
        i_pkt.pts -= o_video_stream->first_dts;
        i_pkt.dts -= o_video_stream->first_dts;
​
        static int num = 1;
        printf("frame %d\n", num++);
        av_interleaved_write_frame(o_fmt_ctx, &i_pkt); //写入文件,并释放对象
//        av_write_frame(o_fmt_ctx, &i_pkt); //写入文件
        //av_free_packet(&i_pkt);
        //av_init_packet(&i_pkt);
        usleep(3000);
        count++;
        if(count>=100) { //只保存了100帧,10s
            break;
        }
    }
​
    avformat_close_input(&i_fmt_ctx);
​
    av_write_trailer(o_fmt_ctx);
​
    avcodec_close(o_fmt_ctx->streams[0]->codec);
    av_freep(&o_fmt_ctx->streams[0]->codec);
    av_freep(&o_fmt_ctx->streams[0]);
​
    avio_close(o_fmt_ctx->pb);
    av_free(o_fmt_ctx);
​
    printf("按任意键停止\n");
    return 0;
}
​
int main(int argc, char **argv)
{
    bStop = false;
​
    std::thread t(rtsp2mp4);
​
    printf("按任意键停止录像\n");
    getchar();
    bStop = true;
    printf("按任意键退出\n");
    getchar();
    t.join();
​
    return 0;
}

文献:

  1. 雷霄骅 ffmpeg:https://blog.csdn.net/leixiaohua1020/column/info/ffmpeg-devel
  2. FFmpeg从入门到精通_刘歧;赵文杰(著)_机械工业出版社
  3. FFmpeg官网http://ffmpeg.org/
  4. GitHub:https://github.com/FFmpeg/FFmpeg

猜你喜欢

转载自blog.csdn.net/Tosonw/article/details/89883591