FFmpeg自學入門筆記

命令行

PS:我自己使用過的命令行,便於自己查閱和使用FFmpeg。

1.轉格式

ffmpeg -i input.mp4 output.avi

2.轉分辨率

ffmpeg -i in.mp4 -vf scale=640:480 -qscale 9 out.mp4
注:-qscale參數表示圖像質量,1是最高質量。


《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
#依賴:mfx_dispatch,需要源碼編譯
$ git clone https://github.com/lu-zero/mfx_dispatch.git
$ cd mfx_dispatch/
$ mkdir build
$ cd build/
$ cmake -D__ARCH:STRING=intel64 ..
$ make -j12
$ sudo make install
#接下來要修改:/usr/lib/pkgconfig/libmfx.pc
$ gedit /usr/lib/pkgconfig/libmfx.pc
#原:
Name: libmfx
Description: Intel(R) Media SDK Dispatcher by Toson
Version: 1.27
prefix=/opt/intel/mediasdk
libdir=/opt/intel/mediasdk/lib
includedir=/opt/intel/mediasdk/include
Libs: -L${libdir} -lmfx -lstdc++ -ldl -lrt -lva -lva-drm
Cflags: -I${includedir}
#修改爲:
Name: libmfx
Description: Intel(R) Media SDK Dispatcher by Toson
Version: 1.27
prefix=/usr/local
exec_prefix=${prefix}
libdir=${prefix}/lib
includedir=${prefix}/include
Libs: -L${libdir} -lmfx -ldispatch_shared -lva -lva-drm -lsupc++ -lstdc++
-ldl
Cflags: -I${includedir}

FFmpeg編譯:

$ mkdir build
$ cd build
# 配置
$ ../configure --enable-shared --disable-static
# 如果想要在 FFMPEG 中編譯 QSV 硬件編碼器,則需要使用下面命令進行配置:
$ ../configure --enable-libmfx --enable-encoder=h264_qsv --enable-decoder=h264_qsv --enable-shared --disable-static
# 編譯
$ make -j12
# 安裝
$ 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
#ffmpeg轉存網絡視頻流到本地
$ ffmpeg -i http://....7b_sd.mp4 -acodec copy -b 1680 out_1_1.mp4
#將視頻文件轉換成h264的1920x1080的文件
$ ffmpeg -i 輸入文件名 -s 1920x1080 -vcodec h264 -i 輸出文件名
# 提取音頻流
$ ffmpeg -i 你的視頻文件名 -f wav 你要保存的文件名.wav -y
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
# 我自己使用的簡單的轉換
$ ffmpeg -i in.mp4 -vf scale=640:480 out.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音頻/ …),可以通過過濾進一步處理。在過濾之後,幀被傳遞到編碼器,編碼器對它們進行編碼並輸出編碼的分組。最後,這些傳遞給複用器,複用器將編碼的數據包寫入輸出文件。

四、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

五、測試代碼

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;
}

ffmpeg讀取視頻幀率:

    //讀取視頻幀率
    int  frame_rate = 0;
    if(i_fmt_ctx->streams[0]->r_frame_rate.den > 0)
    { //注:r_frame_rate是流的實際基本幀速率。這是可以準確表示所有時間戳的最低幀速率(它是流中所有幀速率中最不常見的倍數)。注意,這個值只是一個猜測!
      //我測試後發現,r_frame_rate並不是我們需要的視頻幀率。
        //frame_rate = i_fmt_ctx->streams[0]->r_frame_rate.num/i_fmt_ctx->streams[0]->r_frame_rate.den;
        fprintf(stderr, "r_frame_rate.num: %d  .den: %d   ---   ",
                i_fmt_ctx->streams[0]->r_frame_rate.num,
                i_fmt_ctx->streams[0]->r_frame_rate.den);
    }
    if(i_fmt_ctx->streams[0]->avg_frame_rate.den > 0)
    { //注:avg_frame_rate是平均幀速率。
      //經測試,avg_frame_rate纔是我們需要的視頻幀率。
        frame_rate = i_fmt_ctx->streams[0]->avg_frame_rate.num/i_fmt_ctx->streams[0]->avg_frame_rate.den;
        fprintf(stderr, "avg_frame_rate.num: %d  .den: %d   ---   ",
                i_fmt_ctx->streams[0]->avg_frame_rate.num,
                i_fmt_ctx->streams[0]->avg_frame_rate.den);
    }
    if(i_fmt_ctx->streams[0]->codec->framerate.den > 0)
    { //注:ffmpeg官方解釋該變量是:
      //  *-解碼:對於在壓縮比特流中存儲幀速率值的編解碼器,解碼器可以將其導出到此處。0,1未知時。
      //  *-編碼:可用於向編碼器發送CFR內容的幀速率信號。
      //經測試,該值=avg_frame_rate,所以可忽視。
        //frame_rate = i_fmt_ctx->streams[0]->codec->framerate.num/i_fmt_ctx->streams[0]->codec->framerate.den;
        fprintf(stderr, "codec->framerate.num: %d  .den: %d   ---   ",
                i_fmt_ctx->streams[0]->codec->framerate.num,
                i_fmt_ctx->streams[0]->codec->framerate.den);
    }
    fprintf(stderr, "frame_rate: %d \n", frame_rate);

文獻:

  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
發佈了26 篇原創文章 · 獲贊 13 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章