FFMPEG音视频开发指南(一)

2022-10-06 08:23:59 浏览数 (2)

前言

FFmpeg是一款开源软件,用于生成处理多媒体数据的各类库和程序。FFmpeg可以转码、处理视频和图 片(调整视频、图片大小,去噪等)、打包、传输及播放视频。作为最受欢迎的视频和图像处理软件, 早已经被各行各业的不同公司所广泛使用。

当前文章内容分为3个部分。

  1. 安装ffmpeg,通过源码进行编译ffmpeg
  2. 介绍常用的命令行处理,视频转码、摄像头录制、摄像头推流、比如:推流到B站直播间。
  3. Ffmpeg代码开发案例:提供Linux多个代码案例可以直接运行,完成的功能与上面的命令一样。

开发环境介绍:

代码语言:javascript复制
linux操作系统: Red Hat 6.3
FFMEG版本:   3.0.2
虚拟机:  VMware® Workstation 15 Pro
USB摄像头:罗技C270i
笔记本自带摄像头:ThinkPad E480

一、安装X264编码器

1.1 安装Yasm库

在PC机Linux编译X264需要yasm库。

Yasm是一个完全重写的NASM汇编。它支持x86和AMD64指令集。

Yasm库的官网下载地址: Download - The Yasm Modular Assembler Project

配置编译:

代码语言:javascript复制
./configure
make
make install

1.2 安装X264编码器

x264是一个免费软件 库和应用程序,用于将视频流编码为 H.264 / MPEG-4 AVC压缩格式,并根据GNU GPL的条款发布。

下载地址: x264, the best H.264/AVC encoder - VideoLAN

功能概述

(1)、提供一流的性能,压缩和功能。

(2)、实现出色的性能,在一台消费者级别的计算机上实时编码4个或更多1080p流。

(3)、提供最好的质量,具有最先进的心理视觉优化。

(4)、许多不同应用程序所必需的支持功能,例如电视广播,蓝光低延迟视频应用程序和Web视频。

(5)、x264构成了许多网络视频服务的核心,例如Youtube,Facebook,Vimeo和Hulu。它已被电视广播公司和ISP广泛使用。

编码器功能

(1)、8x8和4x4自适应空间变换

(2)、自适应B帧放置

(3)、B帧作为参考/任意帧顺序

(4)、CAVLC / CABAC熵编码

(5)、自定义量化矩阵

(6)、内部:所有宏块类型(具有所有预测的16x16、8x8、4x4和PCM)

(7)、Inter P:所有分区(从16x16到4x4)

(8)、Inter B:从16x16到8x8的分区(包括跳过/直接)

(9)、隔行扫描(MBAFF)

(10)、多个参考系

(11)、速率控制:恒定量化器,恒定质量,单通道或多通道ABR,可选VBV

(12)、场景切换检测

(13)、B帧中的时空直接模式,自适应模式选择

(14)、在多个CPU上并行编码

(15)、预测性无损模式

(16)、用于细节保留的Psy优化(自适应量化,psy-RD,psy-网格)

(17)、任意调整比特率分布的区域

X264库编译配置:

代码语言:javascript复制
[root@wbyq x264-snapshot-20160527-2245]# ./configure --prefix=$PWD/_install --enable-shared --enable-static
[root@wbyq x264-snapshot-20160527-2245]# make install
[root@wbyq x264-snapshot-20160527-2245]# cd _install/
[root@wbyq _install]# tree
.
├── bin
│   └── x264
├── include
│   ├── x264_config.h
│   └── x264.h
└── lib
    ├── libx264.a
    ├── libx264.so -> libx264.so.148
    ├── libx264.so.148
    └── pkgconfig
        └── x264.pc
4 directories, 7 files

1.3 库的路径环境变量

为了方便ffmepg命令执行时,可以找到x264的动态库,可以将生成的动态库拷贝到/usr/lib目录下,或者将库路径加入到LD_LIBRARY_PATH 环境变量里。

二、安装FFMPEG库

2.1 FFMPEG介绍

FFmpeg是领先的多媒体框架,能够解码,编码, 转码,mux,demux,流,过滤和播放几乎所有内容。它支持最模糊的古代格式,直至最前沿。无论它们是由某些标准委员会,社区还是公司设计的。它还具有高度的可移植性:FFmpeg可在各种构建环境,机器体系结构和配置下,跨Linux,Mac OS X,Microsoft Windows,BSD,Solaris等编译,运行并通过我们的测试基础架构 FATE。

它包含可以由应用程序使用的libavcodec,libavutil,libavformat,libavfilter,libavdevice,libswscale和libswresample。与ffmpeg,ffplay和ffprobe一样,最终用户也可以使用它们进行转码和播放。

FFmpeg开发库:

(1)、libavutil是一个包含简化程序功能的库,其中包括随机数生成器,数据结构,数学例程,核心多媒体实用程序等。

(2)、libavcodec是一个库,其中包含音频/视频编解码器的解码器和编码器。

(3)、libavformat是一个包含用于多媒体容器格式的解复用器和复用器的库。

(4)、libavdevice是一个包含输入和输出设备的库,用于从许多常见的多媒体输入/输出软件框架(包Video4Linux,Video4Linux2,VfW和ALSA)中获取和呈现。

(5)、libavfilter是一个包含媒体过滤器的库。

(6)、libswscale是一个执行高度优化的图像缩放和颜色空间/像素格式转换操作的库。

(7)、libswresample是一个执行高度优化的音频重采样,重矩阵化和样本格式转换操作的库。

2.2 下载编译FFMPEG(3.0.2与4.2.2)

下载地址: https://ffmpeg.org/download.html

配置FFMPEG:

代码语言:javascript复制
[root@wbyq ffmpeg-3.0.2]#./configure --enable-shared --enable-static --prefix=$PWD/_install --enable-gpl --extra-cflags=-I/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/include --extra-ldflags=-L/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/lib --enable-ffserver --enable-ffmpeg --enable-libx264

编译安装:

代码语言:javascript复制
[root@wbyq ffmpeg-3.0.2]# make
[root@wbyq ffmpeg-3.0.2]# make install

FFMPEG最新版本4.2.2编译配置:

截止编写文档时,FFMPEG最新版本是4.2.2。

 图2-2-1

编译配置方法如下:

代码语言:javascript复制
[root@wbyq ffmpeg-4.2.2]# ./configure --enable-static --enable-shared --prefix=$PWD/_install --extra-cflags=-I/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/include --extra-ldflags=-L/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/lib --enable-ffmpeg --enable-libx264 --enable-gpl
[root@wbyq ffmpeg-4.2.2]#make
[root@wbyq ffmpeg-4.2.2]#make install

编译完成之后,将生成的动态库文件拷贝到/usr/lib目录下,可执行文件拷贝到/usr/bin目录下,方便后期调用。

代码语言:javascript复制
[root@wbyq /home/wbyq/pc_work/ffmpeg-3.0.2/_install/lib]# cp *.so* /usr/lib
[root@wbyq /home/wbyq/pc_work/ffmpeg-3.0.2/_install/bin]# cp ffmpeg /usr/bin/

查看当前FFMPEG版本信息:

2.3 当前编译环境介绍

ffmpeg version 3.0.2 Copyright (c) 2000-2016 the FFmpeg developers

  built with gcc 4.4.6 (GCC) 20120305 (Red Hat 4.4.6-4)

  configuration: --enable-shared --enable-static --prefix=/home/wbyq/pc_work/ffmpeg-3.0.2/_install --enable-gpl --extra-cflags=-I/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/include --extra-ldflags=-L/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/lib --enable-ffserver --enable-ffmpeg --enable-libx264

  libavutil      55. 17.103 / 55. 17.103

  libavcodec     57. 24.102 / 57. 24.102

  libavformat    57. 25.100 / 57. 25.100

  libavdevice    57. 0.101 / 57. 0.101

  libavfilter     6. 31.100 / 6. 31.100

  libswscale      4. 0.100 / 4. 0.100

  libswresample   2. 0.101 / 2. 0.101

  libpostproc    54. 0.100 / 54. 0.100

linux操作系统: Red Hat 6.3

FFMEG版本:   3.0.2

虚拟机:  VMware® Workstation 15 Pro

USB摄像头:罗技C270i

笔记本自带摄像头:ThinkPad E480

三、ffmpeg命令行的常用用法(3.0.2)

3.1 ffmpeg命令介绍

Ffmpeg源码编译完成之后,会生成一个ffmpeg可执行文件。

ffmpeg是一个非常快速的视频和音频转换器,也可以从实时音频/视频源中获取。它还可以在任意采样率之间转换,并使用高质量的多相滤波器即时调整视频大小。

ffmpeg从该选项指定的任意数量的输入“文件”(可以是常规文件,管道,网络流,抓取设备等)中读取 -i,并写入任意数量的由以下参数指定的输出“文件”一个普通的输出网址。在命令行上找到的所有不能解释为选项的内容都被视为输出URL。

每个输入或输出URL原则上都可以包含任意数量的不同类型的流(视频/音频/字幕/附件/数据)。流的允许数量和/或类型可能会受到容器格式的限制。选择是从哪个输入流进入哪个输出,是自动完成还是通过-map选项进行选择(请参阅“流选择”一章)。

要在选项中引用输入文件,必须使用其索引(从0开始)。例如,第一个输入文件是0,第二个输入文件是,1等等。类似地,文件中的流由其索引引用。例如,2:3引用第三输入文件中的第四流。另请参阅“流说明符”一章。

通常,选项将应用于下一个指定的文件。因此,顺序很重要,您可以在命令行上多次使用相同的选项。然后,将每次出现都应用于下一个输入或输出文件。该规则的例外是全局选项(例如,详细级别),应首先指定。

不要混合输入文件和输出文件–首先指定所有输入文件,然后指定所有输出文件。也不要混用属于不同文件的选项。所有选项仅适用于下一个输入或输出文件,并且在文件之间重置。

3.2 使用ffmpeg命令推流视频文件到B站

先到B站注册账号,开通直播间,在右上角头像--个人中心进入直播间。

图3-1

图3-2

图3-3

图3-4

命令行执行命令:

代码语言:javascript复制
[root@wbyq ffmpeg-3.0.2]# ./ffmpeg -re -i "/mnt/hgfs/linux-share-dir/123.mp4" -c copy -vcodec libx264 -acodec aac -f flv "<你的rtmp地址><你的直播码>" 

参数解析:
-vcodec libx264 指定视频编码格式
-acodec aac 指定音频编码格式
推流给B站的视频,一定要指定视频编码为x264,音频aac否则可能导致传递过去的视频无法播放,或者无法推流。
这里的rtmp地址和直播码,需要替换成自己B站的地址。
-i 参数是指定视频源文件。
推流成功之后,在自己的直播间可以看到推流的视频。

自己的直播间地址,在B站个人中心—我的直播间选项里可以看到。

图3-5

3.3 视频和音频单独抓取

如果指定输入格式和设备,则ffmpeg可以直接捕获视频和音频。

Linux下捕获摄像头的数据保存成视频文件:

代码语言:javascript复制
# ffmpeg -f video4linux2 -s 1280x720 -i /dev/video0 test.mp4
参数介绍:
-s 指定摄像头输出的图像尺寸
-i 摄像头的设备节点
test.mp4 是保存的视频文件名称
-f video4linux2是指定框架

Linux下捕获声卡的数据保存成音频文件:

代码语言:javascript复制
(1)# ffmpeg -f alsa -ac 2 -ar 44100 -i default out.wav
参数介绍:
-i 指定声卡设备名称。这里default表示选择默认声卡。
out.wav 捕获的音频数据保存的文件名称
-f 是指定音频驱动类型。alsa是linux下音频驱动框架。 oss是另外一种音频框架。
-ar <freq> 设置音频采样率,以HZ为单位
-ac <channels> 设置音频通道数(单声道、双声道)
(2)# ffmpeg -f alsa -ac 1 -ar 44100 -i default -t 30 out.wav
参数介绍:
-t 30 表示录制30秒就自动停止

(3)# ffmpeg -f alsa -ac 1 -ar 16000 -i hw:0 -t 10 out.wav
参数介绍:
	这里的hw:0 也表示选择默认的声卡设备录音。

列出当前主机上的声卡设备:

代码语言:javascript复制
[root@wbyq linux-share-dir]# arecord -l   (列出声卡设备数量)
**** List of CAPTURE Hardware Devices ****
card 0: AudioPCI [Ensoniq AudioPCI], device 0: ES1371/1 [ES1371 DAC2/ADC]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: U0x46d0x825 [USB Device 0x46d:0x825], device 0: USB Audio [USB Audio]
  Subdevices: 0/1
  Subdevice #0: subdevice #0

[root@wbyq linux-share-dir]# arecord -L   (列出声卡设备详细信息)
default
    Default
front:CARD=AudioPCI,DEV=0
    Ensoniq AudioPCI, ES1371 DAC2/ADC
    Front speakers
surround40:CARD=AudioPCI,DEV=0
    Ensoniq AudioPCI, ES1371 DAC2/ADC
    4.0 Surround output to Front and Rear speakers
iec958:CARD=AudioPCI,DEV=0
    Ensoniq AudioPCI, ES1371 DAC2/ADC
    IEC958 (S/PDIF) Digital Audio Output
front:CARD=U0x46d0x825,DEV=0
    USB Device 0x46d:0x825, USB Audio
    Front speakers
surround40:CARD=U0x46d0x825,DEV=0
    USB Device 0x46d:0x825, USB Audio
    4.0 Surround output to Front and Rear speakers
surround41:CARD=U0x46d0x825,DEV=0
    USB Device 0x46d:0x825, USB Audio
    4.1 Surround output to Front, Rear and Subwoofer speakers
surround50:CARD=U0x46d0x825,DEV=0
    USB Device 0x46d:0x825, USB Audio
    5.0 Surround output to Front, Center and Rear speakers
surround51:CARD=U0x46d0x825,DEV=0
    USB Device 0x46d:0x825, USB Audio
    5.1 Surround output to Front, Center, Rear and Subwoofer speakers
surround71:CARD=U0x46d0x825,DEV=0
    USB Device 0x46d:0x825, USB Audio
    7.1 Surround output to Front, Center, Side, Rear and Woofer speakers
iec958:CARD=U0x46d0x825,DEV=0
    USB Device 0x46d:0x825, USB Audio
    IEC958 (S/PDIF) Digital Audio Output

使用arecord -L命令列出了声卡名字之后,就可以选择指定声卡录制声音,其中front:xxx 就是声卡的名字。

选择指定的声卡录制声音示例:

代码语言:javascript复制
(1). 选择USB摄像头的音频设备录音
# ffmpeg -f alsa -ac 1 -ar 44100 -i front:CARD=U0x46d0x825,DEV=0 -t 10 out.wav

(2). 选择电脑自带的声卡录音
# ffmpeg -f alsa -ac 1 -ar 44100 -i front:CARD=AudioPCI,DEV=0 -t 10 out.wav

FFMPEG录制音频的其他参数:

代码语言:javascript复制
# ffmpeg --help
音频选项:
-aframes number 设置要输出的音频帧数
-aq quality 设置音频质量(特定于编解码器)
-ar rate 设置音频采样率(以Hz为单位)
-ac channel 设置音频通道数
-an 禁用音频
-acodec codec 强制音频编解码器复制到流
-vol volume   更改音频音量(256=正常)
-af filter_graph 设置音频过滤器

3.4 录制带声音的视频

命令示例:

代码语言:javascript复制
#ffmpeg -f alsa -ac 1 -ar 44100 -i front:CARD=U0x46d0x825,DEV=0 -f video4linux2 -i /dev/video0 out.mpg

#ffmpeg -f alsa -ac 1 -ar 16000 -i front:CARD=U0x46d0x825,DEV=0 -f video4linux2 -i /dev/video0 out.mp4
录制MP4格式的视频时,音频采样率设置16000效果比较好一些。
采用MP4格式录制视频的详细信息(视频H264、音频AAC):
Output #0, mp4, to 'out.mp4':
  Metadata:
    encoder         : Lavf57.25.100
    Stream #0:0: Video: h264 (libx264) ([33][0][0][0] / 0x0021), yuv422p, 640x480, q=-1--1, 30 fps, 15360 tbn, 30 tbc
    Metadata:
      encoder         : Lavc57.24.102 libx264
    Side data:
      unknown side data type 10 (24 bytes)
    Stream #0:1: Audio: aac (LC) ([64][0][0][0] / 0x0040), 16000 Hz, mono, fltp, 69 kb/s
    Metadata:
      encoder         : Lavc57.24.102 aac
Stream mapping:
  Stream #1:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
  Stream #0:0 -> #0:1 (pcm_s16le (native) -> aac (native)) 

采用MPG格式录制视频的详细信息(视频mpeg1video、音频mp2):

代码语言:javascript复制
Output #0, mpeg, to 'out.mpg':
  Metadata:
    encoder         : Lavf57.25.100
    Stream #0:0: Video: mpeg1video, yuv420p, 640x480, q=2-31, 200 kb/s, 30 fps, 90k tbn, 30 tbc
    Metadata:
      encoder         : Lavc57.24.102 mpeg1video
    Side data:
      unknown side data type 10 (24 bytes)
    Stream #0:1: Audio: mp2, 48000 Hz, mono, s16, 384 kb/s
    Metadata:
      encoder         : Lavc57.24.102 mp2
Stream mapping:
  Stream #1:0 -> #0:0 (rawvideo (native) -> mpeg1video (native))
  Stream #0:0 -> #0:1 (pcm_s16le (native) -> mp2 (native))

3.5 视频与图片之间互转

视频转为图片:

代码语言:javascript复制
# ffmpeg -i 123.mp4 image_%d.jpg
将123.mp4的视频每一帧画面保存为一张张图片。

图片转为视频:

代码语言:javascript复制
# ffmpeg -f image2 -i image_%d.jpg video.mpg

3.6 列出FFMPEG支持的编码器

代码语言:javascript复制
[root@wbyq ffmpeg_video]# ffmpeg -encoders
ffmpeg version 4.2.2 Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 4.4.6 (GCC) 20120305 (Red Hat 4.4.6-4)
  configuration: --enable-static --enable-shared --prefix=/home/wbyq/pc_work/ffmpeg-4.2.2/_install --extra-cflags=-I/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/include --extra-ldflags=-L/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/lib --enable-ffmpeg --enable-libx264 --enable-gpl
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Encoders:
 V..... = Video
 A..... = Audio
 S..... = Subtitle
 .F.... = Frame-level multithreading
 ..S... = Slice-level multithreading
 ...X.. = Codec is experimental
 ....B. = Supports draw_horiz_band
 .....D = Supports direct rendering method 1
 ------
 V..... a64multi             Multicolor charset for Commodore 64 (codec a64_multi)
 V..... a64multi5            Multicolor charset for Commodore 64, extended with 5th color (colram) (codec a64_multi5)
 V..... alias_pix            Alias/Wavefront PIX image
 V..... amv                  AMV Video
 V..... apng                 APNG (Animated Portable Network Graphics) image
 V..... asv1                 ASUS V1
 V..... asv2                 ASUS V2
 V..... avrp                 Avid 1:1 10-bit RGB Packer
 V..X.. avui                 Avid Meridien Uncompressed
 V..... ayuv                 Uncompressed packed MS 4:4:4:4
 V..... bmp                  BMP (Windows and OS/2 bitmap)
 V..... cinepak              Cinepak
 V..... cljr                 Cirrus Logic AccuPak
 V.S... vc2                  SMPTE VC-2 (codec dirac)
 VFS... dnxhd                VC3/DNxHD
 V..... dpx                  DPX (Digital Picture Exchange) image
 VFS... dvvideo              DV (Digital Video)
 V.S... ffv1                 FFmpeg video codec #1
 VF.... ffvhuff              Huffyuv FFmpeg variant
 V..... fits                 Flexible Image Transport System
 V..... flashsv              Flash Screen Video
 V..... flashsv2             Flash Screen Video Version 2
 V..... flv                  FLV / Sorenson Spark / Sorenson H.263 (Flash Video) (codec flv1)
 V..... gif                  GIF (Graphics Interchange Format)
 V..... h261                 H.261
 V..... h263                 H.263 / H.263-1996
 V.S... h263p                H.263  / H.263-1998 / H.263 version 2
 V..... libx264              libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (codec h264)
 V..... libx264rgb           libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 RGB (codec h264)
 VF.... huffyuv              Huffyuv / HuffYUV
 V..... jpeg2000             JPEG 2000
 VF.... jpegls               JPEG-LS
 VF.... ljpeg                Lossless JPEG
 VF.... magicyuv             MagicYUV video
 VFS... mjpeg                MJPEG (Motion JPEG)
 V.S... mpeg1video           MPEG-1 video
 V.S... mpeg2video           MPEG-2 video
 V.S... mpeg4                MPEG-4 part 2
 V..... msmpeg4v2            MPEG-4 part 2 Microsoft variant version 2
 V..... msmpeg4              MPEG-4 part 2 Microsoft variant version 3 (codec msmpeg4v3)
 V..... msvideo1             Microsoft Video-1
 V..... pam                  PAM (Portable AnyMap) image
 V..... pbm                  PBM (Portable BitMap) image
 V..... pcx                  PC Paintbrush PCX image
 V..... pgm                  PGM (Portable GrayMap) image
 V..... pgmyuv               PGMYUV (Portable GrayMap YUV) image
 VF.... png                  PNG (Portable Network Graphics) image
 V..... ppm                  PPM (Portable PixelMap) image
 VF.... prores               Apple ProRes
 VF.... prores_aw            Apple ProRes (codec prores)
 VFS... prores_ks            Apple ProRes (iCodec Pro) (codec prores)
 V..... qtrle                QuickTime Animation (RLE) video
 V..... r10k                 AJA Kona 10-bit RGB Codec
 V..... r210                 Uncompressed RGB 10-bit
 V..... rawvideo             raw video
 V..... roqvideo             id RoQ video (codec roq)
 V..... rv10                 RealVideo 1.0
 V..... rv20                 RealVideo 2.0
 V..... sgi                  SGI image
 V..... snow                 Snow
 V..... sunrast              Sun Rasterfile image
 V..... svq1                 Sorenson Vector Quantizer 1 / Sorenson Video 1 / SVQ1
 V..... targa                Truevision Targa image
 VF.... tiff                 TIFF image
 VF.... utvideo              Ut Video
 V..... v210                 Uncompressed 4:2:2 10-bit
 V..... v308                 Uncompressed packed 4:4:4
 V..... v408                 Uncompressed packed QT 4:4:4:4
 V..... v410                 Uncompressed 4:4:4 10-bit
 V..... wmv1                 Windows Media Video 7
 V..... wmv2                 Windows Media Video 8
 V..... wrapped_avframe      AVFrame to AVPacket passthrough
 V..... xbm                  XBM (X BitMap) image
 V..... xface                X-face image
 V..... xwd                  XWD (X Window Dump) image
 V..... y41p                 Uncompressed YUV 4:1:1 12-bit
 V..... yuv4                 Uncompressed packed 4:2:0
 VF.... zlib                 LCL (LossLess Codec Library) ZLIB
 V..... zmbv                 Zip Motion Blocks Video
 A..... aac                  AAC (Advanced Audio Coding)
 A..... ac3                  ATSC A/52A (AC-3)
 A..... ac3_fixed            ATSC A/52A (AC-3) (codec ac3)
 A..... adpcm_adx            SEGA CRI ADX ADPCM
 A..... g722                 G.722 ADPCM (codec adpcm_g722)
 A..... g726                 G.726 ADPCM (codec adpcm_g726)
 A..... g726le               G.726 little endian ADPCM ("right-justified") (codec adpcm_g726le)
 A..... adpcm_ima_qt         ADPCM IMA QuickTime
 A..... adpcm_ima_wav        ADPCM IMA WAV
 A..... adpcm_ms             ADPCM Microsoft
 A..... adpcm_swf            ADPCM Shockwave Flash
 A..... adpcm_yamaha         ADPCM Yamaha
 A..... alac                 ALAC (Apple Lossless Audio Codec)
 A..... aptx                 aptX (Audio Processing Technology for Bluetooth)
 A..... aptx_hd              aptX HD (Audio Processing Technology for Bluetooth)
 A..... comfortnoise         RFC 3389 comfort noise generator
 A..X.. dca                  DCA (DTS Coherent Acoustics) (codec dts)
 A..... eac3                 ATSC A/52 E-AC-3
 A..... flac                 FLAC (Free Lossless Audio Codec)
 A..... g723_1               G.723.1
 A..X.. mlp                  MLP (Meridian Lossless Packing)
 A..... mp2                  MP2 (MPEG audio layer 2)
 A..... mp2fixed             MP2 fixed point (MPEG audio layer 2) (codec mp2)
 A..... nellymoser           Nellymoser Asao
 A..X.. opus                 Opus
 A..... pcm_alaw             PCM A-law / G.711 A-law
 A..... pcm_dvd              PCM signed 16|20|24-bit big-endian for DVD media
 A..... pcm_f32be            PCM 32-bit floating point big-endian
 A..... pcm_f32le            PCM 32-bit floating point little-endian
 A..... pcm_f64be            PCM 64-bit floating point big-endian
 A..... pcm_f64le            PCM 64-bit floating point little-endian
 A..... pcm_mulaw            PCM mu-law / G.711 mu-law
 A..... pcm_s16be            PCM signed 16-bit big-endian
 A..... pcm_s16be_planar     PCM signed 16-bit big-endian planar
 A..... pcm_s16le            PCM signed 16-bit little-endian
 A..... pcm_s16le_planar     PCM signed 16-bit little-endian planar
 A..... pcm_s24be            PCM signed 24-bit big-endian
 A..... pcm_s24daud          PCM D-Cinema audio signed 24-bit
 A..... pcm_s24le            PCM signed 24-bit little-endian
 A..... pcm_s24le_planar     PCM signed 24-bit little-endian planar
 A..... pcm_s32be            PCM signed 32-bit big-endian
 A..... pcm_s32le            PCM signed 32-bit little-endian
 A..... pcm_s32le_planar     PCM signed 32-bit little-endian planar
 A..... pcm_s64be            PCM signed 64-bit big-endian
 A..... pcm_s64le            PCM signed 64-bit little-endian
 A..... pcm_s8               PCM signed 8-bit
 A..... pcm_s8_planar        PCM signed 8-bit planar
 A..... pcm_u16be            PCM unsigned 16-bit big-endian
 A..... pcm_u16le            PCM unsigned 16-bit little-endian
 A..... pcm_u24be            PCM unsigned 24-bit big-endian
 A..... pcm_u24le            PCM unsigned 24-bit little-endian
 A..... pcm_u32be            PCM unsigned 32-bit big-endian
 A..... pcm_u32le            PCM unsigned 32-bit little-endian
 A..... pcm_u8               PCM unsigned 8-bit
 A..... pcm_vidc             PCM Archimedes VIDC
 A..... real_144             RealAudio 1.0 (14.4K) (codec ra_144)
 A..... roq_dpcm             id RoQ DPCM
 A..X.. s302m                SMPTE 302M
 A..... sbc                  SBC (low-complexity subband codec)
 A..X.. sonic                Sonic
 A..X.. sonicls              Sonic lossless
 A..X.. truehd               TrueHD
 A..... tta                  TTA (True Audio)
 A..X.. vorbis               Vorbis
 A..... wavpack              WavPack
 A..... wmav1                Windows Media Audio 1
 A..... wmav2                Windows Media Audio 2
 S..... ssa                  ASS (Advanced SubStation Alpha) subtitle (codec ass)
 S..... ass                  ASS (Advanced SubStation Alpha) subtitle
 S..... dvbsub               DVB subtitles (codec dvb_subtitle)
 S..... dvdsub               DVD subtitles (codec dvd_subtitle)
 S..... mov_text             3GPP Timed Text subtitle
 S..... srt                  SubRip subtitle (codec subrip)
 S..... subrip               SubRip subtitle
 S..... text                 Raw text subtitle
 S..... webvtt               WebVTT subtitle
 S..... xsub                 DivX subtitles (XSUB)

3.7 推流本地摄像头视频音频到流媒体服务器(4.2.2)

(1). 在红帽6.3系统上运行: 推流本地实时音频视频到流媒体服务器

示例:

代码语言:javascript复制
[wbyq@wbyq linux_c]$ ffmpeg -f video4linux2  -r 12 -s 640x480 -i /dev/video0 -f alsa -i default -ar 44100 -ac 1 -f mp3 -qscale 10 -f flv "rtmp://47.92.114.13:8086/live/123"

参数解析:
-f video4linux2 指定linux下的视频驱动框架
-s 640x480  指定视频尺寸
-i /dev/video0 摄像头节点
f alsa 声卡驱动框架
-i default  选择声卡,这里选择默认声卡
-ar 44100  声音采样频率
-ac 1   单声道
-f mp3  MP3音频
-qscale 10 设置画面质量,值越小画面质量越高
f flv   指定封装格式
"rtmp://47.92.114.13:8086/live/123"  流媒体服务器地址

(2). 推流同时保存视频到本地

示例:

代码语言:javascript复制
[wbyq@wbyq linux-share-dir]$ ffmpeg -thread_queue_size 128 -f video4linux2  -r 12 -s 1280x720 -i /dev/video0 -f alsa -i default -ar 44100 -ac 1 -f mp3 -qscale 5 -f flv "rtmp://47.92.114.13:8086/live/123" output.h264

参数解析:
当这个任务消耗有点大时,-thread_queue_size 必须设置一个比较大的值,不然会看到 FFmpeg输出的日志信息中不停的提醒:[video4linux2,v4l2 @ 0x25fbc40] Thread message queue blocking; consider raising the thread_queue_size option (current value: 8),拍摄到的视频也会出现莫名其妙的错误,比如帧率很高,无法正常播放,视频不流畅等等。把 -thread_queue_size 设置为一个比较大的值,直到看不到该提示即可。

下面截图是使用ffmpeg将本地摄像头和声卡的数据推流到自己搭建的流媒体服务器之后,再使用VLC软件拉流进行显示,也可以使用Mplayer播放器进行拉流显示

Linux下搭建流媒体服务器:ginx rtmp ffmpeg 可以实现。

图3-7-1 windows下使用VLC软件拉流显示效果

Mplayer命令拉流显示流媒体视频示例:

代码语言:javascript复制
mplayer -framedrop rtmp://47.92.114.13:8086/live/123

图3-7-2 linux下使用Mplayer拉流显示效果

服务器也可以直接采用B站的服务器,直接在B站注册账号,实名认证,开通直播功能,获取直播地址,然后直接推流即可,步骤可以参考3.2小节。

示例代码:

代码语言:javascript复制
[wbyq@wbyq linux-share-dir]$ ffmpeg -thread_queue_size 128 -f video4linux2  -r 12 -s 1280x720 -i /dev/video0 -f alsa -i default -ar 44100 -ac 1 -f mp3 -qscale 5 -vcodec libx264 -acodec aac -f flv "rtmp://js.live-send.acg.tv/live-js/?streamname=live_68130189_71037877&key=b95d4cfda0c196518f104839fe5e7573"

参数解析:
vcodec libx264  指定视频编码为x264格式
-acodec aac     指定音频编码为aac

注意:推流到B站的实时视频,一定要指定视频编码为X264,音频为aac,否则推流过去可能会显示不出来。

图3-7-2

3.8 给推流视频添加水印

(1). 添加静态水印

代码语言:javascript复制
ffmpeg -re -i "/mnt/hgfs/linux-share-dir/Video_2020-01-18_1.wmv" -c copy -vcodec libx264 -acodec aac -vf drawtext="fontfile=arial.ttf:x=w-tw:fontcolor=red:fontsize=80:text='XiaoLong肖龙'" -f flv "rtmp://js.live-send.acg.tv/live-js/?streamname=live_68130189_71037877&key=b95d4cfda0c196518f104839fe5e7573"

参数介绍:
上面黄色底色部分代码是添加水印的代码。
fontcolor=red:设置颜色为红色
fontsize=80    设置字体大小为80
text='DS小龙哥' 设置显示的文本

(2). 添加动态时间水印

示例1: 在右上角添加时间水印

代码语言:javascript复制
ffmpeg -re -i "/mnt/hgfs/linux-share-dir/Video_2020-01-18 1.wmv" -c copy -vcodec libx264 -acodec aac -vf drawtext="expansion=strftime:basetime=$(date  %s -d '2020-02-27 16:06:21'):fontfile=arial.ttf:x=w-tw:fontcolor=red:fontsize=30:text='%Y-%m-%d  %H\:%M\: %S" -f flv "rtmp://js.live-send.acg.tv/live-js/?streamname=live_68130189_71037877&key=b95d4cfda0c196518f104839fe5e7573"

示例2:

代码语言:javascript复制
ffmpeg -re -i "/mnt/hgfs/linux-share-dir/Video_2020-01-18_贪吃蛇游戏制作_1.wmv" -c copy -vcodec libx264 -acodec aac -vf drawtext="expansion=strftime:basetime=$(date  %s -d '2020-02-27 16:06:21'):fontfile=arial.ttf:x=w-tw:fontcolor=red:fontsize=80:text='XiaoLong肖龙 %Y-%m-%d  %H\:%M\: %S" -f flv "rtmp://js.live-send.acg.tv/live-js/?streamname=live_68130189_71037877&key=b95d4cfda0c196518f104839fe5e7573" 

四、ffmpeg代码开发示例

4.1 FFMPE采集摄像头数据保存(3.0.2版本)

FFmpeg中有一个和多媒体设备交互的类库:Libavdevice。

使用这个库可以读取电脑(或者其他设备上)的多媒体设备的数据或者输出数据到指定的多媒体设备上。

最简单的例子,调用Libavdevice库读取摄像头的一帧YUV数据,并保存成output.yuv文件。

Libavdevice支持以下设备作为输入端:

代码语言:javascript复制
alsa
avfoundation
bktr
dshow
dv1394
fbdev
gdigrab
iec61883
jack
lavfi
libcdio
libdc1394
openal
oss
pulse
qtkit
sndio
video4linux2, v4l2
vfwcap
x11grab
decklink

Libavdevice支持以下设备作为输出端:

代码语言:javascript复制
alsa
caca
decklink
fbdev
opengl
oss
pulse
sdl
sndio
xv

示例代码: 采集一帧摄像头的数据,保存到本地

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"

int main(int argc, char* argv[])  
{  
    AVFormatContext *pFormatCtx;  
    int             i, videoindex;  
    AVCodecContext  *pCodecCtx;  
    AVCodec         *pCodec;  
      
    av_register_all();  
    avformat_network_init();  
    pFormatCtx=avformat_alloc_context();  
    
    //注册设备 
    avdevice_register_all();  
      
    //Linux  
    AVInputFormat *ifmt=av_find_input_format("video4linux2");  
    if(avformat_open_input(&pFormatCtx,"/dev/video0",ifmt,NULL)!=0)
    {  
        printf("无法打开输入流。/dev/video0n");  
        return -1;  
    }
    if(avformat_find_stream_info(pFormatCtx,NULL)<0)  
    {  
        printf("找不到流信息。n");  
        return -1;  
    }  
    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("找不到视频流.n");  
        return -1;  
    }  
    pCodecCtx=pFormatCtx->streams[videoindex]->codec;  
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);  
    if(pCodec==NULL)  
    {  
        printf("找不到编解码器。n");  
        return -1;  
    }  
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)  
    {  
        printf("无法打开编解码器。n");  
        return -1;  
    }  
    AVFrame *pFrame,*pFrameYUV;  
    pFrame=av_frame_alloc();  
    pFrameYUV=av_frame_alloc();  
    unsigned char *out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,16));   // avpicture_get_size
    
    av_image_fill_arrays((AVPicture *)pFrameYUV->data,(AVPicture *)pFrameYUV->linesize,out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,16); 

    //根据获取摄像头的宽高和指定的像素格式420,分配空间
    printf("摄像头尺寸:width=%d height=%d n",pCodecCtx->width, pCodecCtx->height);  
    int screen_w=0,screen_h=0;  
    int ret, got_picture;  
    AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));  

    FILE *fp_yuv=fopen("output.yuv","wb ");      

    struct SwsContext *img_convert_ctx;  
    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);   
    //配置图像格式转换以及缩放参数
    if(av_read_frame(pFormatCtx, packet)>=0)
    {  
        //输出图像的大小
        printf("数据大小=%dn",packet->size);
        //将数据写入文件
        if(packet->stream_index==videoindex)
        {  
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);  //解码从摄像头获取的数据,pframe结构
            if(ret < 0)
            {  
                printf("解码Error.n");  
                return -1;  
            }  
            if(got_picture)
            {  
                sws_scale(img_convert_ctx,(const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);  //根据前面配置的缩放参数,进行图像格式转换以及缩放等操作
                int y_size=pCodecCtx->width*pCodecCtx->height;  
                
                fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y     
                fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U    
                fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V    
            }  
        }  
        av_packet_unref(packet);       
    }  
    sws_freeContext(img_convert_ctx);  
    fclose(fp_yuv);  
    av_free(out_buffer);  
    av_free(pFrameYUV);  
    avcodec_close(pCodecCtx);  
    avformat_close_input(&pFormatCtx);  
    return 0;  
}

Makefile文件代码:

代码语言:javascript复制
app:
gcc ffmpeg_video.c -I /home/wbyq/pc_work/ffmpeg-3.0.2/_install/include -L /home/wbyq/pc_work/ffmpeg-3.0.2/_install/lib -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale

编译运行代码示例:

代码语言:javascript复制
[wbyq@wbyq ffmpeg_video]$ make
gcc -o app ffmpeg_video.c -I /home/wbyq/pc_work/ffmpeg-3.0.2/_install/include -L /home/wbyq/pc_work/ffmpeg-3.0.2/_install/lib -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale -lm -L/home/wbyq/pc_work/x264-snapshot-20160527-2245/_install/lib -lx264
[wbyq@wbyq ffmpeg_video]$ ./app 
摄像头尺寸:width=640 height=480 
数据大小=614400

说明:

如果使用其他版本的FFMPEG编译本代码,出现如下警告:不建议使用‘xxxxxx’(声明于 xxxxx:xxx),

直接打开当前使用的FFMPEG源码,找到推荐的函数,替换旧函数即可

由于保存的图片是YUV格式,不能使用常规图片软件查看,windows下可以下载“7yuv”软件进行查看。

7yuv是一种方便的工具,用于编辑和可视化原始图形数据和二进制文件。它是辅助开发游戏,视频编解码器和常规图形编程的宝贵工具。 支持多种表面格式,包括RGB和YUV像素格式。 7yuv打开任何文件,无论类型或大小。数据以原始二进制格式处理,7yuv允许“位真编辑”。可以在图形,十六进制或文本模式下编辑数据。

图4-1-1

4.2 FFMPEG读取摄像头数据并编码保存视频(4.2.2版本)

常见的视频封装器与编码器的对应关系:

图4-2-1

使用FFMPEG命令捕获摄像头数据录制成视频:

代码语言:javascript复制
[wbyq@wbyq ffmpeg_video]$ ffmpeg -f video4linux2 -s 1280x720 -i /dev/video0 -vcodec libx264 -f flv test.flv
参数解析:
-f <flv> 指定封装器名称。指定了封装器,不管文件后缀是什么,都会采用指定的封装器来封装文件。
-vcodec <libx264> 指定编码器 
-i </dev/video0>  指定摄像头设备的节点
-f <video4linux2> 指定设备类型
-s <1280x720>     指定图像的尺寸

下面代码,参考源码/doc/example/muxing.c例子,采集摄像头一帧帧YUV数据,经过编码,封装成并保存成视频文件。

示例代码:

代码语言:javascript复制
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <math.h>  
#include <libavutil/avassert.h>  
#include <libavutil/channel_layout.h>  
#include <libavutil/opt.h>  
#include <libavutil/mathematics.h>  
#include <libavutil/timestamp.h>  
#include <libavformat/avformat.h>  
#include <libswscale/swscale.h>  
#include <libswresample/swresample.h>  

#define STREAM_DURATION   50.0 /*录制视频的持续时间  秒*/
#define STREAM_FRAME_RATE 5     /* images/s  这里可以根据摄像头的采集速度来设置帧率 */
#define STREAM_PIX_FMT    AV_PIX_FMT_YUV420P /* default pix_fmt */  
#define SCALE_FLAGS SWS_BICUBIC  

//存放视频的宽度和高度
int video_width;
int video_height;

// 单个输出AVStream的包装器 
typedef struct OutputStream
{  
    AVStream *st;  
    AVCodecContext *enc;  
    /*下一帧的点数*/  
    int64_t next_pts;  
    int samples_count;  
    AVFrame *frame;  
    AVFrame *tmp_frame;  
    float t, tincr, tincr2;  
    struct SwsContext *sws_ctx;  
    struct SwrContext *swr_ctx;  
}OutputStream;  

typedef struct IntputDev
{  
    AVCodecContext  *pCodecCtx;  
    AVCodec         *pCodec;  
    AVFormatContext *v_ifmtCtx;  
    int  videoindex;  
    struct SwsContext *img_convert_ctx;  
    AVPacket *in_packet;  
    AVFrame *pFrame,*pFrameYUV;  
}IntputDev;  

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)  
{  
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;  
    printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%dn",  
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),  
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),  
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),  
           pkt->stream_index);  
}  

static int write_frame(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt)  
{  
    /* 将输出数据包时间戳值从编解码器重新调整为流时基 */  
    av_packet_rescale_ts(pkt, *time_base, st->time_base);  
    pkt->stream_index = st->index;  

    /*将压缩的帧写入媒体文件。*/  
    log_packet(fmt_ctx, pkt);  
    return av_interleaved_write_frame(fmt_ctx, pkt);  
}  

/*添加输出流。 */  
static void add_stream(OutputStream *ost, AVFormatContext *oc,AVCodec **codec,enum AVCodecID codec_id)  
{  
    AVCodecContext *c;  
    int i;  
    /* find the encoder */ 

    *codec = avcodec_find_encoder(codec_id);  
    if (!(*codec))
    {  
        fprintf(stderr, "Could not find encoder for '%s'n",  
                avcodec_get_name(codec_id));  
        exit(1);  
    }  
    ost->st = avformat_new_stream(oc, NULL);  

    if (!ost->st) {  
        fprintf(stderr, "Could not allocate streamn");  
        exit(1);  
    }  
    ost->st->id = oc->nb_streams-1;  
    c = avcodec_alloc_context3(*codec);  
    if (!c) {  
        fprintf(stderr, "Could not alloc an encoding contextn");  
        exit(1);  
    }  
    ost->enc = c;   
    switch((*codec)->type)
    {  
        case AVMEDIA_TYPE_AUDIO:  
            c->sample_fmt  = (*codec)->sample_fmts ?  
                (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;  
            c->bit_rate    = 64000;  
            c->sample_rate = 44100;  
            if ((*codec)->supported_samplerates) {  
                c->sample_rate = (*codec)->supported_samplerates[0];  
                for (i = 0; (*codec)->supported_samplerates[i]; i  ) {  
                    if ((*codec)->supported_samplerates[i] == 44100)  
                        c->sample_rate = 44100;  
                }  
            }  
            c->channels        = av_get_channel_layout_nb_channels(c->channel_layout);  
            c->channel_layout = AV_CH_LAYOUT_STEREO;  
            if ((*codec)->channel_layouts) {  
                c->channel_layout = (*codec)->channel_layouts[0];  
                for (i = 0; (*codec)->channel_layouts[i]; i  ) {  
                    if ((*codec)->channel_layouts[i] == AV_CH_LAYOUT_STEREO)  
                        c->channel_layout = AV_CH_LAYOUT_STEREO;  
                }  
            }  
            c->channels        = av_get_channel_layout_nb_channels(c->channel_layout);  
            ost->st->time_base = (AVRational){ 1, c->sample_rate };  
            break;  
      
        case AVMEDIA_TYPE_VIDEO:  
            c->codec_id = codec_id;  
            c->bit_rate = 2500000;  //平均比特率,例子代码默认值是400000
            /* 分辨率必须是2的倍数。*/  
            c->width=video_width;  
            c->height=video_height;  
            /*时基:这是基本的时间单位(以秒为单位)
             *表示其中的帧时间戳。 对于固定fps内容,
             *时基应为1 /framerate,时间戳增量应为
             *等于1。*/  
            ost->st->time_base = (AVRational){1,STREAM_FRAME_RATE};  //帧率设置
            c->time_base       = ost->st->time_base;  
            c->gop_size      = 12; /* 最多每十二帧发射一帧内帧 */  
            c->pix_fmt       = STREAM_PIX_FMT;  
            if(c->codec_id == AV_CODEC_ID_MPEG2VIDEO)
            {  
                /* 只是为了测试,我们还添加了B帧 */  
                c->max_b_frames = 2;  
            }  
            if(c->codec_id == AV_CODEC_ID_MPEG1VIDEO)
            {  
                /*需要避免使用其中一些系数溢出的宏块。
                 *普通视频不会发生这种情况,因为
                 *色度平面的运动与亮度平面不匹配。 */ 
                c->mb_decision = 2;  
            }   
        break;  
      
        default:  
            break;  
    }  
  
    /* 某些格式希望流头分开。 */  
    if (oc->oformat->flags & AVFMT_GLOBALHEADER)  
        c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;    
}  

static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)  
{  
    AVFrame *picture;  
    int ret;  
    picture = av_frame_alloc();  
    if (!picture)  
        return NULL;  
    picture->format = pix_fmt;  
    picture->width  = width;  
    picture->height = height;  
  
    /* 为帧数据分配缓冲区 */  
    ret = av_frame_get_buffer(picture, 32);  
    if(ret<0)
    {  
        fprintf(stderr, "Could not allocate frame data.n");  
        exit(1);  
    }  
    return picture;  
}  
  
static void open_video(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)  
{  
    int ret;  
    AVCodecContext *c = ost->enc;  
    AVDictionary *opt = NULL;  
  
    av_dict_copy(&opt, opt_arg, 0);  
  
    /* open the codec */  
    ret = avcodec_open2(c, codec, &opt);  
    av_dict_free(&opt);  
    if (ret < 0)
    {  
        fprintf(stderr, "Could not open video codec: %sn", av_err2str(ret));  
        exit(1);  
    }  
  
    /* 分配并初始化可重用框架 */  
    ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);  
    if (!ost->frame)
    {  
        fprintf(stderr, "Could not allocate video framen");  
        exit(1);  
    }  
    printf("ost->frame alloc success fmt=%d w=%d h=%dn",c->pix_fmt,c->width, c->height);  
  
  
    /*如果输出格式不是YUV420P,则为临时YUV420P
    *也需要图片。 然后将其转换为所需的
    *输出格式。 */
    ost->tmp_frame = NULL;  
    if(c->pix_fmt != AV_PIX_FMT_YUV420P)
    {  
        ost->tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height);  
        if (!ost->tmp_frame)
        {  
            fprintf(stderr, "Could not allocate temporary picturen");  
            exit(1);  
        }  
    }  
  
    /* 将流参数复制到多路复用器*/  
    ret=avcodec_parameters_from_context(ost->st->codecpar, c);  
    if(ret<0)
    {  
        fprintf(stderr, "Could not copy the stream parametersn");  
        exit(1);  
    }  
}

/*
  *编码一个视频帧
  *编码完成后返回1,否则返回0
  */  
static int write_video_frame(AVFormatContext *oc, OutputStream *ost,AVFrame *frame)  
{  
    int ret;  
    AVCodecContext *c;  
    int got_packet=0;  
    AVPacket pkt={0};  
    if(frame==NULL)  
        return 1;  
    c = ost->enc;  
    av_init_packet(&pkt);  
    /* 编码图像*/  
    ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);  
    if(ret<0)
    {  
        fprintf(stderr, "Error encoding video frame: %sn", av_err2str(ret));  
        exit(1);  
    }  
    printf("--------------video- pkt.pts=%sn",av_ts2str(pkt.pts));  
    printf("----st.num=%d st.den=%d codec.num=%d codec.den=%d---------n",ost->st->time_base.num,ost->st->time_base.den,  
            c->time_base.num,c->time_base.den);         
    if(got_packet)
    {  
        ret = write_frame(oc, &c->time_base, ost->st, &pkt);  
    }else
    {  
        ret = 0;  
    }  
    if(ret<0)
    {  
        fprintf(stderr, "Error while writing video frame: %sn", av_err2str(ret));  
        exit(1);  
    }  
    return (frame || got_packet) ? 0 : 1;  
}  
  
  
static AVFrame *get_video_frame(OutputStream *ost,IntputDev* input,int *got_pic)  
{  
    int ret, got_picture;  
    AVCodecContext *c = ost->enc;  
    AVFrame * ret_frame=NULL;  
    if(av_compare_ts(ost->next_pts, c->time_base,STREAM_DURATION, (AVRational){1,1})>=0)  
        return NULL;  
  
    /*当我们将帧传递给编码器时,它可能会保留对它的引用
    *内部,确保我们在这里不覆盖它*/
    if (av_frame_make_writable(ost->frame)<0)  
        exit(1);  
    if(av_read_frame(input->v_ifmtCtx, input->in_packet)>=0)
    {  
        if(input->in_packet->stream_index==input->videoindex)
        {  
            ret = avcodec_decode_video2(input->pCodecCtx, input->pFrame, &got_picture, input->in_packet);  
            *got_pic=got_picture;  
            if(ret<0)
            {  
                printf("Decode Error.n");  
                av_packet_unref(input->in_packet);  
                return NULL;  
            }  
            if(got_picture)
            {  
                sws_scale(input->img_convert_ctx, (const unsigned char* const*)input->pFrame->data, input->pFrame->linesize, 0, input->pCodecCtx->height, ost->frame->data,  ost->frame->linesize);  
                ost->frame->pts =ost->next_pts  ;  
                ret_frame= ost->frame;    
            }  
        }  
        av_packet_unref(input->in_packet);  
    }  
    return ret_frame;  
}  
static void close_stream(AVFormatContext *oc, OutputStream *ost)  
{  
    avcodec_free_context(&ost->enc);  
    av_frame_free(&ost->frame);  
    av_frame_free(&ost->tmp_frame);  
    sws_freeContext(ost->sws_ctx);  
    swr_free(&ost->swr_ctx);  
}  

/*
采集摄像头数据编码成MP4视频
*/  
int main(int argc, char **argv)  
{  
    OutputStream video_st = { 0 }, audio_st = { 0 };  
    const char *filename;  
    AVOutputFormat *fmt;  
    AVFormatContext *oc;  
    AVCodec *audio_codec, *video_codec;  
    int ret;  
    int have_video = 0, have_audio = 0;  
    int encode_video = 0, encode_audio = 0;  
    AVDictionary *opt = NULL;  
    int i;  
  
    if(argc<3)
    {  
        //./app /dev/video0 123.mp4
        printf("用法: %s <摄像头设备节点> <文件名称> n", argv[0]);  
        return 1;  
    }  
  
    filename = argv[2];  
    printf("当前存储的视频文件名称:%sn",filename);
    /*分配输出媒体环境*/
    avformat_alloc_output_context2(&oc, NULL, NULL, filename);  
    if(!oc)
    {  
        printf("无法从文件扩展名推断出输出格式:使用MPEG。n");  
        avformat_alloc_output_context2(&oc, NULL, "mpeg", filename);  
    }  
    if(!oc)return 1;  
    //添加摄像头----------------------------------
    IntputDev video_input={0};  
    AVCodecContext  *pCodecCtx;  
    AVCodec         *pCodec;  
    AVFormatContext *v_ifmtCtx;  
    avdevice_register_all();  
    v_ifmtCtx = avformat_alloc_context();  
    //Linux下指定摄像头信息  
    AVInputFormat *ifmt=av_find_input_format("video4linux2");  
    if(avformat_open_input(&v_ifmtCtx,argv[1],ifmt,NULL)!=0)
    {  
        printf("无法打开输入流.%sn",argv[1]);  
        return -1;  
    }
    if(avformat_find_stream_info(v_ifmtCtx,NULL)<0)  
    {  
        printf("找不到流信息.n");  
        return -1;  
    }
    int videoindex=-1;  
    for(i=0; i<v_ifmtCtx->nb_streams; i  )   
    if(v_ifmtCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)  
    {  
        videoindex=i;  
        printf("videoindex=%dn",videoindex);
        break;  
    }
    if(videoindex==-1)  
    {  
        printf("找不到视频流。n");  
        return -1;  
    }  
    pCodecCtx=v_ifmtCtx->streams[videoindex]->codec; 
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);  
    if(pCodec==NULL)  
    {  
        printf("找不到编解码器。n");  
        return -1;  
    }  
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)  
    {  
        printf("无法打开编解码器。n");  
        return -1;  
    }  
  
    AVFrame *pFrame,*pFrameYUV;  
    pFrame=av_frame_alloc();  
    pFrameYUV=av_frame_alloc();
    
    unsigned char *out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,16));  
    av_image_fill_arrays((AVPicture *)pFrameYUV->data,(AVPicture *)pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,16);  
    printf("摄像头尺寸(WxH): %d x %d n",pCodecCtx->width, pCodecCtx->height);  
    video_width=pCodecCtx->width;
    video_height=pCodecCtx->height;
    struct SwsContext *img_convert_ctx;  
    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);   
    AVPacket *in_packet=(AVPacket *)av_malloc(sizeof(AVPacket));  
    video_input.img_convert_ctx=img_convert_ctx;  
    video_input.in_packet=in_packet;  
    video_input.pCodecCtx=pCodecCtx;  
    video_input.pCodec=pCodec;  
    video_input.v_ifmtCtx=v_ifmtCtx;  
    video_input.videoindex=videoindex;  
    video_input.pFrame=pFrame;  
    video_input.pFrameYUV=pFrameYUV;  
    //-----------------------------添加摄像头结束
    fmt=oc->oformat;  
    /*使用默认格式的编解码器添加音频和视频流并初始化编解码器。*/ 
    printf("fmt->video_codec = %dn", fmt->video_codec);  
    if(fmt->video_codec != AV_CODEC_ID_NONE)
    {  
        add_stream(&video_st,oc,&video_codec,fmt->video_codec);  
        have_video=1;  
        encode_video=1;  
    }  
    /*现在已经设置了所有参数,可以打开音频并视频编解码器,并分配必要的编码缓冲区。*/
    if(have_video)open_video(oc, video_codec, &video_st, opt);  
    av_dump_format(oc,0,filename,1);  
    /* 打开输出文件(如果需要) */  
    if(!(fmt->flags & AVFMT_NOFILE))
    {  
        ret=avio_open(&oc->pb,filename,AVIO_FLAG_WRITE);  
        if(ret<0)
        {  
            fprintf(stderr, "打不开'%s': %sn", filename,av_err2str(ret));  
            return 1;  
        }  
    }  
    /* 编写流头(如果有)*/  
    ret=avformat_write_header(oc, &opt);  
    if(ret<0)
    {  
        fprintf(stderr, "打开输出文件时发生错误: %sn",av_err2str(ret));  
        return 1;  
    }  
    int got_pic;  
    while(encode_video)
    {  
        /*选择要编码的流*/    
        AVFrame *frame=get_video_frame(&video_st,&video_input,&got_pic);  
        if(!got_pic)  
        {  
            usleep(10000);  
            continue;  
        }
        encode_video=!write_video_frame(oc,&video_st,frame);  
    }  
    av_write_trailer(oc);  
    sws_freeContext(video_input.img_convert_ctx);  
    avcodec_close(video_input.pCodecCtx);  
    av_free(video_input.pFrameYUV);  
    av_free(video_input.pFrame);      
    avformat_close_input(&video_input.v_ifmtCtx); 
    /*关闭每个编解码器*/  
    if (have_video)close_stream(oc, &video_st);
    /*关闭输出文件*/ 
    if (!(fmt->flags & AVFMT_NOFILE))avio_closep(&oc->pb);  
    /*释放流*/  
    avformat_free_context(oc); 
    return 0;  
}
Makefile文件代码:
app:
    gcc ffmpeg_video.c -I /home/wbyq/pc_work/ffmpeg-4.2.2/_install/include -L /home/wbyq/pc_work/ffmpeg-4.2.2/_install/lib -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale
运行示例:
[root@wbyq ffmpeg_video]# make
gcc ffmpeg_video.c -I /home/wbyq/pc_work/ffmpeg-4.2.2/_install/include -L /home/wbyq/pc_work/ffmpeg-4.2.2/_install/lib -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale
ffmpeg_video.c: 在函数‘write_video_frame’中:
ffmpeg_video.c:248: 警告:不建议使用‘avcodec_encode_video2’(声明于 /home/wbyq/pc_work/ffmpeg-4.2.2/_install/include/libavcodec/avcodec.h:5462)
ffmpeg_video.c: 在函数‘get_video_frame’中:
ffmpeg_video.c:289: 警告:不建议使用‘avcodec_decode_video2’(声明于 /home/wbyq/pc_work/ffmpeg-4.2.2/_install/include/libavcodec/avcodec.h:4828)
ffmpeg_video.c: 在函数‘main’中:
ffmpeg_video.c:371: 警告:不建议使用‘codec’(声明于 /home/wbyq/pc_work/ffmpeg-4.2.2/_install/include/libavformat/avformat.h:885)
ffmpeg_video.c:382: 警告:不建议使用‘codec’(声明于 /home/wbyq/pc_work/ffmpeg-4.2.2/_install/include/libavformat/avformat.h:885)

[root@wbyq ffmpeg_video]# ./a.out /dev/video1 1.mp4
当前存储的视频文件名称:1.mp4
videoindex=0
摄像头尺寸(WxH): 1280 x 720 
fmt->video_codec = 27
[libx264 @ 0x8795a80] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3
[libx264 @ 0x8795a80] profile High, level 3.1
[libx264 @ 0x8795a80] 264 - core 148 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=3 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=12 keyint_min=1 scenecut=40 intra_refresh=0 rc_lookahead=12 rc=abr mbtree=1 bitrate=2500 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
ost->frame alloc success fmt=0 w=1280 h=720
Output #0, mp4, to '1.mp4':
Stream #0:0: Video: h264, yuv420p, 1280x720, q=2-31, 2500 kb/s, 5 tbn
………………省略…………..

4.3 FFMPEG使用代码方式推流视频到流媒体服务器

推流本地视频文件到流媒体服务器,与3.2章节的命令效果是一样的。

示例代码:

代码语言:javascript复制
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <signal.h>

int main(int argc, char * argv[])
{    
    AVFormatContext *pInFmtContext = NULL;    
    AVStream *in_stream;
    AVCodecContext *pInCodecCtx;
    AVCodec *pInCodec;
    AVPacket *in_packet;
    AVFormatContext * pOutFmtContext;
    AVOutputFormat *outputFmt;
    AVStream * out_stream;
    AVRational frame_rate; 
    double duration;
    int ret;
    char in_file[128] = {0};
    char out_file[256] = {0};
    int videoindex = -1;
    int audioindex = -1;
    int video_frame_count = 0;
    int audio_frame_count = 0;
    int video_frame_size = 0;
    int audio_frame_size = 0;
    int i;
    int got_picture;
    if(argc < 2)
    {
        printf("Usage: a.out <in_filename> <url>n");
        return -1;
    }
    memcpy(in_file, argv[1], strlen(argv[1]));
    memcpy(out_file, argv[2], strlen(argv[2]));
    if(avformat_open_input ( &pInFmtContext, in_file, NULL, NULL) < 0)
    {
        printf("avformat_open_input failedn");         
        return -1;    
    }
    
    //查询输入流中的所有流信息
    if(avformat_find_stream_info(pInFmtContext, NULL) < 0)
    {
        printf("avformat_find_stream_info failedn");
        return -1;
    }
    //print 
    av_dump_format(pInFmtContext, 0, in_file, 0); 
    ret = avformat_alloc_output_context2(&pOutFmtContext, NULL, "flv", out_file);
    if(ret < 0)
    {
        printf("avformat_alloc_output_context2 failedn");
        return -1;
    }        
    for(i=0; i < pInFmtContext->nb_streams; i  )
    {
        in_stream = pInFmtContext->streams[i];
        if( in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audioindex = i;
        }
        if( in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoindex = i;
            frame_rate = av_guess_frame_rate(pInFmtContext, in_stream, NULL);
            printf("video: frame_rate:%d/%dn", frame_rate.num, frame_rate.den);
            printf("video: frame_rate:%d/%dn", frame_rate.den, frame_rate.num);
            duration = av_q2d((AVRational){frame_rate.den, frame_rate.num});       
        }
        
        pInCodec = avcodec_find_decoder(in_stream->codecpar->codec_id);
        printf("%x, %dn", pInCodec, in_stream->codecpar->codec_id);
        out_stream = avformat_new_stream(pOutFmtContext,  pInCodec);//in_stream->codec->codec);
        if( out_stream == NULL)
        {
            printf("avformat_new_stream failed:%dn",i);
        }
 
        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
        if( ret < 0)
        {
            printf("avcodec_parameters_copy failed:%dn", i);
        }
        out_stream->codecpar->codec_tag = 0;
        if(pOutFmtContext->oformat->flags & AVFMT_GLOBALHEADER)
        {//AVFMT_GLOBALHEADER代表封装格式包含“全局头”(即整个文件的文件头),大部分封装格式是这样的。一些封装格式没有“全局头”,比如MPEG2TS
            out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        }        
    }
    av_dump_format(pOutFmtContext,0,out_file,1); 
    ret=avio_open(&pOutFmtContext->pb, out_file, AVIO_FLAG_WRITE);
    if(ret<0)
    {
        printf("avio_open failed:%dn", ret);
        return -1;
    }
    int64_t start_time = av_gettime();
    ret = avformat_write_header(pOutFmtContext, NULL);
    in_packet = av_packet_alloc();
    while(1)
    {
        ret=av_read_frame(pInFmtContext, in_packet);
        if(ret<0)
        {
            printf("read frame endn");
            break;
        }
        in_stream = pInFmtContext->streams[in_packet->stream_index];
        if(in_packet->stream_index == videoindex)
        {
            video_frame_size  = in_packet->size;
            printf("recv ] video frame ]-]n",   video_frame_count, in_packet->size, video_frame_size);
        }
 
        if(in_packet->stream_index == audioindex)
        {
            audio_frame_size  = in_packet->size;
            printf("recv ] audio frame ]-]n",   audio_frame_count, in_packet->size, audio_frame_size);
        }
        
        int codec_type = in_stream->codecpar->codec_type;
        if(codec_type == AVMEDIA_TYPE_VIDEO)
        {
            //根据pts时间与系统时间的关系来计算延时时间,该方案更优
            AVRational  dst_time_base = {1, AV_TIME_BASE};
            int64_t pts_time=av_rescale_q(in_packet->pts,in_stream->time_base, dst_time_base); 
            int64_t now_time=av_gettime() - start_time;
            if(pts_time > now_time)
            av_usleep(pts_time - now_time);
        }
        out_stream = pOutFmtContext->streams[in_packet->stream_index];
        av_packet_rescale_ts(in_packet,in_stream->time_base, out_stream->time_base);
        in_packet->pos = -1;
        ret = av_interleaved_write_frame(pOutFmtContext, in_packet);
        if(ret<0)
        {
            printf("av_interleaved_write_frame failedn");
            break;
        }
        av_packet_unref(in_packet); 
    }
    av_write_trailer(pOutFmtContext);
    av_packet_free(&in_packet);
    avformat_close_input(&pInFmtContext);
    avio_close( pOutFmtContext->pb);
    avformat_free_context(pOutFmtContext);
    return 0;
}

Makefile文件代码:

代码语言:javascript复制
app:
    gcc ffmpeg_video.c -I /home/wbyq/pc_work/ffmpeg-4.2.2/_install/include -L /home/wbyq/pc_work/ffmpeg-4.2.2/_install/lib -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale

编译运行示例:

代码语言:javascript复制
[root@wbyq ffmpeg_video]# ./a.out /mnt/hgfs/linux-share-dir/1-39-508.mp4 rtmp://js.live-send.acg.tv/live-js/?streamname=live_68130189_71037877&key=b95d4cfda0c196518f104839fe5e7573

4.4 QT调用FFMPEG库捕获一帧摄像头图像

第一步: 在QT的工程文件(xxx.pro)里添加FFMPEG和X264的头文件、库文件路径(MiGW编译器)

代码语言:javascript复制
#指定库的路径
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lavcodec
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lavfilter
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lavutil
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lavdevice
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lavformat
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lpostproc
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lswscale
unix:LIBS  = -L/home/wbyq/work_pc/ffmpeg-4.2.2/_install/lib -lswresample
unix:LIBS  = -L/home/wbyq/work_pc/x264-snapshot-20181217-2245/_install/lib -lx264

#制定头文件的路径
INCLUDEPATH =/home/wbyq/work_pc/ffmpeg-4.2.2/_install/include
INCLUDEPATH =/home/wbyq/work_pc/x264-snapshot-20181217-2245/_install/include

如果在C 代码里穿插C语言代码可以在头文件里指定使用C语言方式调用即可。(MiGW编译器)

代码语言:javascript复制
#指定库的路径
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lavcodec
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lavfilter
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lavutil
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lavdevice
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lavformat
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lpostproc
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lswscale
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lswresample
unix:LIBS  = -L$$PWD/ffmpeg_x264_lib/lib -lx264

#制定头文件的路径
INCLUDEPATH =$$PWD/ffmpeg_x264_lib/include

再加路径的时候的可以使用$$PWD获取当前路径,方便填路径:

代码语言:javascript复制
extern "C"
{
    #include "ffmpeg_get_image.h"
}

如果是MSVC编译器,可以使用#pragma comment(lib,...)这种方式导入lib。

直接在cpp代码里编写代码,加入库:

代码语言:javascript复制
#pragma comment(lib,"libavcodec.a")
#pragma comment(lib,"libavfilter.a")
#pragma comment(lib,"libavutil.a")
#pragma comment(lib,"libswresample.a")
#pragma comment(lib,"libx264.a")
#pragma comment(lib,"libavdevice.a")
#pragma comment(lib,"libavformat.a")
#pragma comment(lib,"libavdevice.a")
#pragma comment(lib,"libpostproc.a")
#pragma comment(lib,"libswscale.a")

0 人点赞