Vite 完整版详解

Posted 勒布朗-前端

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vite 完整版详解相关的知识,希望对你有一定的参考价值。

目录

序论:

vite架子分析

1、 打包构建:

2、环境变量

3、模式

4、兼容老浏览器  

5、typescript相关

6、基本配置

核心配置全集


推荐两个插件插件Volar 、 Vue 3 Snippets

序论:

开发环境:ESM+HMR:来实现模块的热更新;类似于webpack-server
生产环境:Rollup: 打包工具rollup的产生就是针对开发js库的,生成代码只是把我们的代码转码成目标js并无其他

Vite 的快,主要体现在两个方面: 快速的冷启动和快速的热更新。而 Vite 之所以能有如此优秀的表现,完全归功于 Vite 借助了浏览器对 ESM 规范的支持,采取了与 Webpack 完全不同的 unbundle 机制。

1.快速冷启动:Vite只启动一台静态页面的服务器,不会打包全部项目文件代码,服务器根据客户端的请求加载不同的模块处理,实现按需加载,而我们所熟知的webpack则是,一开始就将整个项目都打包一遍,再开启dev-server,如果项目规模庞大,打包时间必然很长。

2.打包编译速度:当需要打包到⽣产环境时,vite使⽤传统的rollup进⾏打包,所以,vite的优势是体现在开发阶段,另外,由于vite使⽤的是ES Module,所以代码中不可以使⽤CommonJs;

3.热模块更新:在HRM热更新⽅⾯,当某个模块内容改变时,让浏览器去重新请求该模块即可,⽽不是像webpack重新将该模块的所有依赖重新编译;

vite优缺点:vite优点 -unbundle 机制的核心:

  • 模块之间的依赖关系的解析由浏览器实现;
  • 文件的转换由 dev server 的 middlewares 实现并做缓存;
  • 不对源文件做合并捆绑操作;

vite缺点:由于 unbundle 机制,首屏期间、懒加载方面需要额外做以下工作:和 Webpack 对比,Vite 把需要在 dev server 启动过程中完成的工作,转移到了 dev server 响应浏览器请求的过程中,不可避免的导致首屏性能下降。不过首屏性能差只发生在 dev server 启动以后第一次加载页面时发生。之后再 reload 页面时,首屏性能会好很多。原因是 dev server 会将之前已经完成转换的内容缓存起来。

  • 不对源文件做合并捆绑操作,导致大量的 http 请求;
  • dev server 运行期间对源文件做 resolveloadtransformparse 操作;
  • 预构建、二次预构建操作也会阻塞首屏请求,直到预构建完成为止。

vite架子分析

1、 打包构建:

 npm run build

默认情况下,构建会输出到 dist 文件夹中。你可以部署这个 dist 文件夹到任何你喜欢的平台。

本地测试应用

vite preview 命令会在本地启动一个静态 Web 服务器,将 dist 在 http://localhost:4173。这样在本地环境下查看该构建产物是否正常可用就方便多了。你可以通过 --port 参数来配置服务的运行端口。

现在 preview 命令会将服务器运行在 http://localhost:8080

 自动打开浏览器:

  "dev": "vite --open",
  "build": "vue-tsc --noEmit && vite build",
  "preview": "vite preview --open"

值得注意的是 vite preview 用作预览本地构建,而不应直接作为生产服务器。

2、环境变量

 Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量:

import.meta.env.MODE: string 应用运行的模式。

import.meta.env.BASE_URL: string 部署应用时的基本 URL。他由base 配置项决定。

import.meta.env.PROD: boolean 应用是否运行在生产环境。

import.meta.env.DEV: boolean 应用是否运行在开发环境 (永远与 import.meta.env.PROD相反)。

import.meta.env.SSR: boolean 应用是否运行在 server 上。

 自定义环境变量:
  (1)创建文件:已存在的环境变量不会被以下文件中的覆盖

.env  # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载,如.env.production的优先级比.env高
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略

加载的环境变量也会通过 import.meta.env 以字符串形式暴露给客户端源码。

为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。例如下面这些环境变量:

 只有 VITE_SOME_KEY 会被暴露为 import.meta.env.VITE_SOME_KEY 提供给客户端源码,而 DB_PASSWORD 则不会。

  (2)添加类型声明

<reference types="vite/client" />

 interface ImportMetaEnv 
 readonly VITE_APP_TITLE: string
              // 更多环境变量...
 
 
  interface ImportMeta 
  readonly env: ImportMetaEnv
  

3、模式

默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式。这意味着当执行 vite build 时,它会自动加载 .env.production 中可能存在的环境变量:

 在你的应用中,你可以使用 import.meta.env.VITE_APP_TITLE 渲染标题。

 除了默认的developmen和production还可以自定义模式,如可能希望有一个 “staging” (预发布|预上线) 模式,它应该具有类似于生产的行为,但环境变量与生产环境略有不同

 vite build --mode staging
.env.staging文件存放相关模式下的环境变量

4、兼容老浏览器  

默认情况下 Vite 只处理语法转译,且默认不包含任何 polyfill。 通过引入polyfill: polyfill 是一个js库,主要抚平不同浏览器之间对js实现的差异。 可以前往 Polyfill.io 查看,这是一个基于用户浏览器 User-Agent 字符串自动生成 polyfill 包的服务 通过插件支持:通过插件@vitejs/plugin-legacy来支持,它将自动生成传统版本的 chunk 及与其相对应 ES 语言特性方面的 polyfill,兼容版的chunk只会在不支持原生 ESM 的浏览器中进行按需加载

5、typescript相关

 (1)esbuild下不支持功能编辑时报错
  配置类型导入导出、enum、没有import、export导入导出的非模块文件报错

tsconfig.json
        
       "compilerOptions": 
       "isolatedModules":true 上述特性会在编辑时报错,否则会在运行时报错
            
 

(2)导入vite内置的一些类型定义
资源导入 (例如:导入一个 .svg 文件)

.import.meta.env 上 Vite 注入的环境变量的类型定义
.import.meta.hot 上的 HMR API 类型定义

  方式一:在.d.ts 中      

<reference types="vite/client" />

  方式二:在tsconfig.json中


    "compilerOptions": 
    "types": ["vite/client"]
              

多页面配置

    ├── package.json
    ├── vite.config.js
    ├── index.html
    ├── main.js
    └── nested
        ├── index.html
        └── nested.js
        
      build: 
        rollupOptions: 
          input: 
            main: resolve(__dirname, 'index.html'),
            nested: resolve(__dirname, 'nested/index.html')
          
        
      

6、基本配置

区分不同环境配置
  command:根据运行的命令区分配置,serve为开发环境,否则为build生产环境
  mode:根据环境区分配置  

export default defineConfig(async ( command, mode ) => 
          const config=await fn();    支持使用Promise
          if (command === 'serve') 
            return  
            
           else if(command='build')
            return 
            
          
)

核心配置全集

export default defineConfig(

  base: "./", //开发或生产环境服务的公共基础路径, 绝对 URL 路径名,例如 /foo/
  // 完整的 URL,例如 https://foo.com/
  // 空字符串或 ./(用于开发环境)
  // 通过命令指定:vite build --base=/my/public/path/
  // 代码中获取base:import.meta.env.BASE_URL全局变量在代码中使用,
  //原样出现(例如import.meta.env['BASE_URL']是无效的)
  root: process.cwd(), // 项目根目录(index.html 文件所在的位置绝对位置或相对位置),默认process.cwd()
  define: 
    __DEV__: 'dev',
  , //定义全局常量替换方式。其中每项在开发环境下会被定义在全局,而在构建时被静态替换

  mode: 'development', // 模式将会把serve和build时的模式都覆盖掉。
  //也可以通过命令行 --mode 选项来重写'development'(serve)、'production'(build)
  plugins: [vue()], // 需要用到的插件数组
  publicDir: 'public', // 静态资源服务的文件夹。该目录中的文件在开发期间在 / 处提供
  //并在构建期间复制到 outDir 的根目录,并且始终按原样提供或复制而无需进行转换。
  //该值可以是文件系统的绝对路径,也可以是相对于项目的根目录的相对路径。默认'public'
  cacheDir: 'node_modules/.vite', // 存储缓存文件的目录。此目录下会存储预打包的依赖项或 vite 
  // 生成的某些缓存文件,使用缓存可以提高性能。如需重新生成缓存文件,你可以使用 --force 命令行选项
  // 或手动删除目录。此选项的值可以是文件的绝对路径,也可以是以项目根目录为基准的相对路径。
  // 当没有检测到 package.json 时,则默认为 .vite。  默认"node_modules/.vite"

  // 解析相关
  resolve: 
    alias: [ // 文件系统路径别名
      
        "@": path.resolve(__dirname, "src"),
      ,
      //或
      
        find: /\\/@\\//, //字符串|正则
        replacement: pathResolve('src') + '/'
      
    ],
    dedupe: [], // 强制 Vite 始终将列出的依赖项解析为同一副本,比如当安装了两个不同版本的依赖,
    // 如vue2和vue3,通过这个声明最终引入的版本  []
    conditions: [], // 解决程序包中 情景导出 时的其他允许条件 [
    //     "exports": 
    //       ".": 
    //         "import": "./index.esm.js",
    //         "require": "./index.cjs.js"
    //       
    //     
    //   ]
    mainFields: [], // 解析包入口点尝试的字段列表 ,根据package.json中的字段,
    // 在不同环境中导入库的入口文件位置
    // import引入的文件对应module中的路径
    // require引入的文件对应main中的路径
    // 默认:['module', 'jsnext:main', 'jsnext','main']
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'], //  默认:['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']导入时想要忽略的扩展名列表 导入时想要省略的扩展名列表。不建议忽略自定义导入类型的扩展名(例如:.vue),因为它会影响 IDE 和类型支持
    preserveSymlinks: false, // 启用此选项会使 Vite 通过原始文件路径(即不跟随符号链接的路径)而不是真正的文件路径(即跟随符号链接后的路径)确定文件身份
    // 默认:false
  ,

  // css相关
  css: 
    modules:  //配置 CSS modules 的行为。选项将被传递给 postcss-modules。
      scopeBehaviour: 'global' | 'local',
      // ...
    ,
    postcss: '', // 内联的 PostCSS 配置 如果提供了该内联配置,Vite 将不会搜索其他 PostCSS 配置源
    preprocessorOptions:  // css的预处理器选项
      scss: 
        additionalData: `$injectedColor: orange;`
      
    
  ,

  // JSON相关
  json: 
    namedExports: true, // 是否支持从.json文件中进行按名导入
    stringify: false, //  开启此项,导入的 JSON 会被转换为 export default JSON.parse("...") 会禁用按名导入
  ,

  //esbuild相关
  esbuild:  // 最常见的用例是自定义 JSX
    jsxFactory: 'h',
    jsxFragment: 'Fragment'
    // ESbuild会被应用在 ts、jsx、tsx 文件,以下选项对要处理的文件类型进行配置
    // include:string | RegExp | (string | RegExp)[]
    // exclude:string | RegExp | (string | RegExp)[]
    // jsxInject:自动为每一个被 ESbuild 转换的文件注入内容
    //     `import React from 'react'` 
  ,

  assetsInclude: ['**/*.gltf'], // 指定额外的 picomatch 模式 作为静态资源处理
  logLevel: 'info', // 调整控制台输出的级别 'info' | 'warn' | 'error' | 'silent'
  clearScreen: true, // 设为 false 可以避免 Vite 清屏而错过在终端中打印某些关键信息
  envDir: '/', // 用于加载 .env 文件的目录
  envPrefix: [], // 以 envPrefix 开头的环境变量会通过 import.meta.env 暴露在你的客户端源码中

  //server相关
  server: 
    host: '127.0.0.1', // 指定服务器应该监听哪个 IP 地址
    port: 5000, // 指定开发服务器端口
    strictPort: true, // 若端口已被占用则会直接退出
    https: false, // 启用 TLS + HTTP/2
    // 当为true:启用 TLS + HTTP/2。注意:当 server.proxy 选项 也被使用时,将会仅使用 TLS。
    // 这个值也可以是一个传递给 https.createServer() 的 选项对象
    open: true, // 启动时自动在浏览器中打开应用程序
    proxy:  // 配置自定义代理规则
      '/api': 
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\\/api/, ''),
        ws: true, //WebSocket
      
    ,
    cors: true, // 配置 CORS
    force: true, // 强制使依赖预构建
    hmr:  // 禁用或配置 HMR 连接 热更新相关
      // false禁用
      protocol: string, //协议
      host: string,
      port: number,
      path: string,
      timeout: number,
      overlay: boolean, //为 false 可以禁用开发服务器错误的屏蔽
      clientPort: number, //只在客户端的情况下覆盖端口,这允许你为 websocket 提供不同的端口,而并非在客户端代码中查找。如果需要在 dev-server 情况下使用 SSL 代理,这非常有用。
      server: Server, //当使用 server.middlewareMode 或 server.https 时,你需将 server.hmr.server 指定为你 HTTP(S) 的服务器,这将通过你的服务器来处理 HMR 的安全连接请求。这在使用自签证书或想通过网络在某端口暴露 Vite 的情况下,非常有用。
    ,
    watch:  // 传递给 chokidar 的文件系统监听器选项 监听文件改变
      // 通过命令:vite build --watch
      ignored: ['!**/node_modules/your-package-name/**'] //      默认会忽略对 .git/ 和 node_modules/ 目录的监听,如果需要对 node_modules/ 内的包进行监听,可以为 server.watch.ignored 赋值一个取反的 glob 模式
      // 其他选项:使用的是rollup的选项配置:https://rollupjs.org/guide/en/#watch-options
    ,
    middlewareMode: '', // 以中间件模式创建 Vite 服务器 'ssr' | 'html'    在SSR中使用
    fs: 
      strict: true, // 限制为工作区 root 路径以外的文件的访问
      allow: [], // 限制哪些文件可以通过 /@fs/ 路径提供服务
      deny: ['.env', '.env.*', '*.pem,crt'], // 用于限制 Vite 开发服务器提供敏感文件的黑名单
    ,
    origin: 'http://127.0.0.1:8080/', // 用于定义开发调试阶段生成资产的 origin
  ,

  //build构建相关
  build: 
    target: ['modules'], // 设置最终构建的浏览器兼容目标   默认:'modules'指支持原生 ES 模块的浏览器。
    //  "esnext" :即假设有原生动态导入支持,并且将会转译得尽可能小:
    //  如果 build.minify 选项为 'terser', 'esnext' 将会强制降级为 'es2019'。
    //  其他情况下将完全不会执行转译。
    // 'es2015':自定义目标也可以是一个 ES 版本
    polyfillModulePreload: true, // 是否自动注入 module preload 的 polyfill true:此 polyfill 会被自动注入到每个 index.html 入口的 proxy 模块中
    outDir: 'dist', // 指定输出路径
    assetsDir: 'assets', // 指定生成静态文件目录
    assetsInlineLimit: '4096', // 小于此阈值的导入或引用资源将内联为 base64 编码
    cssCodeSplit: true, // 启用 CSS 代码拆分
    cssTarget: '', // 允许用户为 CSS 的压缩设置一个不同的浏览器 target 与 build.target 一致
    sourcemap: false, // 构建后是否生成 source map 文件
    rollupOptions: , // 自定义底层的 Rollup 打包配置
    lib: , // 构建为库
    manifest: false, // 当设置为 true,构建后将会生成 manifest.json 文件
    ssrManifest: false, // 构建不生成 SSR 的 manifest 文件
    ssr: undefined, // 生成面向 SSR 的构建
    minify: 'esbuild', // 指定使用哪种混淆器
    terserOptions: , // 传递给 Terser 的更多 minify 选项
    write: true, // 启用将构建后的文件写入磁盘
    emptyOutDir: true, // 构建时清空该目录
    brotliSize: true, // 启用 brotli 压缩大小报告
    chunkSizeWarningLimit: 500, // chunk 大小警告的限制
    watch: null, // 设置为  则会启用 rollup 的监听器
  ,

  // 构建预览preview相关
  preview: 
    port: 5000, // 指定开发服务器端口
    strictPort: true, // 若端口已被占用则会直接退出
    https: false, // 启用 TLS + HTTP/2
    open: true, // 启动时自动在浏览器中打开应用程序
    proxy:  // 配置自定义代理规则
      '/api': 
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\\/api/, '')
      
    ,
    cors: true, // 配置 CORS
  ,
  optimizeDeps: 
    entries: [], // 指定自定义条目——该值需要遵循 fast-glob 模式
    exclude: [], // 在预构建中强制排除的依赖项
    include: [], // 可强制预构建链接的包
    keepNames: false, // true 可以在函数和类上保留 name 属性
  ,

  // SSR相关
  ssr: 
    external: [], // 列出的是要为 SSR 强制外部化的依赖,
    noExternal: '', // 列出的是防止被 SSR 外部化依赖项
    target: 'node', // SSR 服务器的构建目标
  ,

  // Worker相关
  worker: 
    format: iife, //worker bundle 的输出类型。 默认: 'iife'   'es',
    plugins: [], //适用于 worker bundle 的 Vite 插件。 []
    rollupOptions: [], //用于构建 worker bundle 的 Rollup 配置项
  
)

FFMPEG详解(完整版)

一、认识FFMPEG

FFMPEG堪称自由软件中最完备的一套多媒体支持库,它几乎实现了所有当下常见的数据封装格式、多媒体传输协议以及音视频编解码器,堪称多媒体业界的瑞士军刀。因此,对于从事多媒体技术开发的工程师来说,深入研究FFMPEG成为一门必不可少的工作,可以这样说,FFMPEG之于多媒体开发工程师的重要性正如kernel之于嵌入式系统工程师一般。

几个小知识:

  • FFMPEG项目是由法国人Fabrice Bellard发起的,此人也是著名的CPU模拟器项目QEMU的发起者,同时还是圆周率算法纪录的保持者。

  • FF是Fast Forward的意思,翻译成中文是“快进”。

  • FFMPEG的LOGO是一个”Z字扫描”示意图,Z字扫描用于将图像的二维频域数据一维化,同时保证了一维化的数据具备良好的统计特性,从而提高其后要进行的一维熵编码的效率。

关于耻辱厅(Hall of Shame):FFMPEG大部分代码遵循LGPL许可证,如果使用者对FFMpeg进行了修改,要求公布修改的源代码;有少部分代码遵循GPL许可证,要求使用者同时公开使用FFMpeg的软件的源代码。实际上,除去部分具备系统软件开发能力的大型公司(Microsoft、Apple等)以及某些著名的音视频技术提供商(Divx、Real等)提供的自有播放器之外,绝大部分第三方开发的播放器都离不开FFMpeg的支持,像Linux桌面环境中的开源播放器VLC、MPlayer,Windows下的KMPlayer、暴风影音以及Android下几乎全部第三方播放器都是基于FFMPEG的。也有许多看似具备自主技术的播放器,其实也都不声不响地使用了FFMPEG,这种行为被称为“盗窃”,参与“盗窃”的公司则被请入耻辱厅,如于2009年上榜的国产播放器暴风影音、QQ影音。

关于FFMPEG的商业应用:与其他开源软件不同的是,FFMPEG所触及的多媒体编解码算法中有相当一部分处于大量的专利涵盖范围之内,因此,在商业软件中使用FFMPEG必须考虑可能造成的对专利所有者的权利侵犯,这一点在FFMPEG的官方网站也有所提及,所涉及的风险需使用者自行评估应对。

FFMPEG从功能上划分为几个模块,分别为核心工具(libutils)、媒体格式(libavformat)、编解码(libavcodec)、设备(libavdevice)和后处理(libavfilter, libswscale, libpostproc),分别负责提供公用的功能函数、实现多媒体文件的读包和写包、完成音视频的编解码、管理音视频设备的操作以及进行音视频后处理。

使用FFMPEG

这里指FFMPEG提供的命令行(CLI)工具ffmpeg,其使用方法如下(方括号表示可选项,花括号表示必选项目):

ffmpeg [global options] [infile options]['-i' 'infile'] ... [outfile options] 'outfile' ...

参数选项由三部分组成:可选的一组全局参数、一组或多组输入文件参数、一组或多组输出文件参数,其中,每组输入文件参数以‘-i’为结束标记;每组输出文件参数以输出文件名为结束标记。

基本选项

能力集列表

  • -formats:列出支持的文件格式。

  • -codecs:列出支持的编解码器。

  • -decoders:列出支持的解码器。

  • -encoders:列出支持的编码器。

  • -protocols:列出支持的协议。

  • -bsfs:列出支持的比特流过滤器。

  • -filters:列出支持的滤镜。

  • -pix_fmts:列出支持的图像采样格式。

  • -sample_fmts:列出支持的声音采样格式。

常用输入选项

  • -i filename:指定输入文件名。

  • -f fmt:强制设定文件格式,需使用能力集列表中的名称(缺省是根据扩展名选择的)。

  • -ss hh:mm:ss[.xxx]:设定输入文件的起始时间点,启动后将跳转到此时间点然后开始读取数据。

对于输入,以下选项通常是自动识别的,但也可以强制设定。

  • -c codec:指定解码器,需使用能力集列表中的名称。

  • -acodec codec:指定声音的解码器,需使用能力集列表中的名称。

  • -vcodec codec:指定视频的解码器,需使用能力集列表中的名称。

  • -b:v bitrate:设定视频流的比特率,整数,单位bps。

  • -r fps:设定视频流的帧率,整数,单位fps。

  • -s WxH : 设定视频的画面大小。也可以通过挂载画面缩放滤镜实现。

  • -pix_fmt format:设定视频流的图像格式(如RGB还是YUV)。

  • -ar sample rate:设定音频流的采样率,整数,单位Hz。

  • -ab bitrate:设定音频流的比特率,整数,单位bps。

  • -ac channels:设置音频流的声道数目。

常用输出选项

  • -f fmt:强制设定文件格式,需使用能力集列表中的名称(缺省是根据扩展名选择的)。

  • -c codec:指定编码器,需使用能力集列表中的名称(编码器设定为”copy“表示不进行编解码)。

  • -acodec codec:指定声音的编码器,需使用能力集列表中的名称(编码器设定为”copy“表示不进行编解码)。

  • -vcodec codec:指定视频的编码器,需使用能力集列表中的名称(编解码器设定为”copy“表示不进行编解码)。

  • -r fps:设定视频编码器的帧率,整数,单位fps。

  • -pix_fmt format:设置视频编码器使用的图像格式(如RGB还是YUV)。

  • -ar sample rate:设定音频编码器的采样率,整数,单位Hz。

  • -b bitrate:设定音视频编码器输出的比特率,整数,单位bps。

  • -ab bitrate:设定音频编码器输出的比特率,整数,单位bps。

  • -ac channels:设置音频编码器的声道数目。

  • -an 忽略任何音频流。

  • -vn 忽略任何视频流。

  • -t hh:mm:ss[.xxx]:设定输出文件的时间长度。

  • -to hh:mm:ss[.xxx]:如果没有设定输出文件的时间长度的画可以设定终止时间点。

  • ★文末名片可以免费领取音视频开发学习资料,内容包括(C/C++,Linux 服务器开发,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

    见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

流标识

FFMPEG的某些选项可以对一个特定的媒体流起作用,这种情况下需要在选项后面增加一个流标识。流标识允许以下几种格式:

  • 流序号。譬如“:1”表示第二个流。

  • 流类型。譬如“:a“表示音频流,流类型可以和流序号合并使用,譬如“🅰️1”表示第二个音频流。

  • 节目。节目和流序号可以合并使用。

  • 流标识。流标识是一个内部标识号。

假如要设定第二个音频流为copy,则需要指定-codec🅰️1 copy

音频选项

  • -aframes:等价于frames:a,输出选项,用于指定输出的音频帧数目。

  • -aq:等价于q:a,老版本为qscale:a,用于设定音频质量。

  • -atag:等价于tag:a,用于设定音频流的标签。

  • -af:等价于filter:a,用于设定一个声音的后处理过滤链,其参数为一个描述声音后处理链的字符串。

视频选项

  • -vframes:等价于frames:v,输出选项,用于指定输出的视频帧数目。

  • -aspect:设置宽高比,如4:3、16:9、1.3333、1.7777等。

  • -bits_per_raw_sample:设置每个像素点的比特数。

  • -vstats:产生video统计信息。

  • -vf:等价于filter:v,用于设定一个图像的后处理过滤链,其参数为一个描述图像后处理链的字符串。

  • -vtag:等价于tag:v,用于设定视频流的标签。

  • -force_fps:强制设定视频帧率。

  • -force_key_frames:显式控制关键帧的插入,参数为字符串,可以是一个时间戳,也可以是一个“expr:”前缀的表达式。如“-force_key_frames 0:05:00”、“-force_key_frames expr:gte(t,n_forced*5)”

滤镜选项

高级选项

  • -re:要求按照既定速率处理输入数据,这个速率即是输入文件的帧率。

  • -map:指定输出文件的流映射关系。例如 “-map 1:0 -map 1:1”要求将第二个输入文件的第一个流和第二个流写入到输出文件。如果没有-map选项,ffmpeg采用缺省的映射关系。

用例

1、将一个老式的avi文件转成mp4

ffmpeg -i final.avi -acodec copy -vcodec copy final.mp4 

2、从一个视频文件中抽取一帧图像:

ffmpeg -y -i test.mp4 -ss 00:03:22.000 -vframes 1 -an test.jpg

3

ffmpeg -i final.avi -vf scale=640:640 square.avi  

4、使用alsa接口录制一段音频存放到某个wav文件中

ffmpeg -f alsa -i hw:0 -t 100 alsaout.wav

5、使用alsa接口搭建一个个人网络电台

ffmpeg -f alsa -i default -acodec aac -strict -2 -b:a 128k -r 44100 /var/www/data/main.m3u8

6、将一个mp4文件的音视频流实时转码之后发送给某个远程设备,远程设备可以通过http获取的sdp文件来接收rtp媒体数据。

ffmpeg -re -i example.mp4 -acodec copy -vcodec libx264 -s 480x270 -map 0:0 -f rtp rtp://10.131.202.62:1234 -map 0:1 -f rtp rtp://10.131.202.62:1238 > /var/www/live.sdp

编译和裁剪

FFMpeg与大部分GNU软件的编译方式类似,是通过configure脚本来实现编译前定制的。这种途径允许用户在编译前对软件进行裁剪,同时通过对宿主系统和目标系统的自动检测来筛选参与编译的模块并为其设定合适的配置。但是,FFMpeg的configure脚本并非如通常的GNU软件一样通过配置工具自动生成,而是完全由人工编写的。configure脚本生成的config.mak和config.h分别在Makefile和源代码的层次上实现编译的控制。

通过运行“./configure –help”可以了解到脚本支持的参数,这些参数大体分为下面几类:

  • 标准选项——GNU软件例行配置项目如安装路径等。例:–prefix=…,……

  • 列出当前源代码支持的能力集,如编解码器,解复用器,输入输出设备,文件系统等。例:–list-decoders,–list-encoders,……

  • 授权选项:–enable-version3,–enable-gpl,–enable-nofree。代码的缺省授权是LGPL v2,如果要使用以LGPL v3、GPL授权的模块或者某些不遵循自有软件授权协议的模块,必须在运行configure时显式使能相应的选项。

  • 编译、链接选项。例:–disable-static,–enable-shared,…… 缺省配置是生成静态库而不生成动态库,如果希望禁止静态库、生成动态库都需要显式指定。

  • 可执行程序控制选项,决定是否生成ffmpeg、ffplay、ffprobe和ffserver。

  • 模块控制选项,筛选参与编译的模块,包括整个库的筛选,例如:–disable-avdevice;一组模块的筛选,例如:–disable-decoders,单个模块的筛选,如:–disable-decoder=… 等。

  • 专家级选项,允许开发者进行深度定制,如交叉编译环境的配置、自定义编译器参数的设定、指令级优化、debug控制等。

对于–disable、–enable类的控制选项,如果以–disable为前缀,则缺省是enable的,反之亦然。

总之,无论从商业角度还是技术角度出发,使用configure脚本对FFMpeg进行裁剪是最安全的方式,只有针对于某些configure无法满足的定制要求,才需要考虑修改configure脚本——甚至修改configure生成的配置文件。

以下是一个配置实例,实现运行与Android系统中的ffmpeg库的编译:

./configure --prefix=. --cross-prefix=$NDK_TOOLCHAIN_PREFIX --enable-cross-compile --arch=arm --target-os=linux --cpu=cortex-a8 \\
                       --disable-static --enable-shared --enable-pic --disable-ffmpeg --disable-ffplay --disable-ffserver --disable-ffprobe \\
                       --extra-cflags="-I$NDK_PLATFORM/usr/include" \\
                       --extra-ldflags="-nostdlib -Wl,-T,$NDK_PREBUILT/arm-linux-androideabi/lib/ldscripts/armelf_linux_eabi.x \\
                                        -L$NDK_PLATFORM/usr/lib \\
                                        $NDK_PREBUILT/lib/gcc/arm-linux-androideabi/4.4.3/crtbegin.o $NDK_PREBUILT/lib/gcc/arm-linux-androideabi/4.4.3/crtend.o \\
                                        -lc -lm -ldl"

缺省的编译会生成4个可执行文件和9个库:可执行文件包括用于转码的ffmpeg、用于获取媒体信息的ffprobe、用于播放媒体的ffplay和用于推送媒体流的ffserver;库包括avutil、avformat、avcodec、avfilter、avdevice、swresample、swscale、postproc及avresample,其中,核心库有5个,分别为基础库avutil、文件格式及协议库avformat、编解码库avcodec、输入输出设备库avdevice和过滤器avfilter。

深入FFMPEG

示例程序

解码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
#include <sys/time.h>
 
#include "libavutil/avstring.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libavutil/opt.h"
#include "libswscale/swscale.h"
 
#define DECODED_AUDIO_BUFFER_SIZE            192000
 
struct options

    int streamId;
    int frames;
    int nodec;
    int bplay;
    int thread_count;
    int64_t lstart;
    char finput[256];
    char foutput1[256];
    char foutput2[256];
;
 
int parse_options(struct options *opts, int argc, char** argv)

    int optidx;
    char *optstr;
 
    if (argc < 2) return -1;
 
    opts->streamId = -1;
    opts->lstart = -1;
    opts->frames = -1;
    opts->foutput1[0] = 0;
    opts->foutput2[0] = 0;
    opts->nodec = 0;
    opts->bplay = 0;
    opts->thread_count = 0;
    strcpy(opts->finput, argv[1]);
 
    optidx = 2;
    while (optidx < argc)
    
        optstr = argv[optidx++];
        if (*optstr++ != '-') return -1;
        switch (*optstr++)
        
        case 's':  //< stream id
            opts->streamId = atoi(optstr);
            break;
        case 'f':  //< frames
            opts->frames = atoi(optstr);
            break;
        case 'k':  //< skipped
            opts->lstart = atoll(optstr);
            break;
        case 'o':  //< output
            strcpy(opts->foutput1, optstr);
            strcat(opts->foutput1, ".mpg");
            strcpy(opts->foutput2, optstr);
            strcat(opts->foutput2, ".raw");
            break;
        case 'n': //decoding and output options
            if (strcmp("dec", optstr) == 0)
                opts->nodec = 1;
            break;
        case 'p':
            opts->bplay = 1;
            break;
        case 't':
            opts->thread_count = atoi(optstr);
            break;
        default:
            return -1;
        
    
 
    return 0;

 
void show_help(char* program)

    printf("Simple FFMPEG test program\\n");
    printf("Usage: %s inputfile [-sstreamid [-fframes] [-kskipped] [-ooutput_filename(without extension)] [-ndec] [-p] [-tthread_count]]\\n",
           program);
    return;

 
static void log_callback(void* ptr, int level, const char* fmt, va_list vl)

    vfprintf(stdout, fmt, vl);

 
/*
* audio renderer code (oss)
*/
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
 
#define OSS_DEVICE "/dev/dsp0"
 
struct audio_dsp

    int audio_fd;
    int channels;
    int format;
    int speed;
;
int map_formats(enum AVSampleFormat format)

    switch(format)
    
        case AV_SAMPLE_FMT_U8:
            return AFMT_U8;
        case AV_SAMPLE_FMT_S16:
            return AFMT_S16_LE;
        default:
            return AFMT_U8; 
    

int set_audio(struct audio_dsp* dsp)

    if (dsp->audio_fd == -1)
    
        printf("Invalid audio dsp id!\\n");
        return -1;
        
 
    if (-1 == ioctl(dsp->audio_fd, SNDCTL_DSP_SETFMT, &dsp->format))
    
        printf("Failed to set dsp format!\\n");
        return -1;
    
 
    if (-1 == ioctl(dsp->audio_fd, SNDCTL_DSP_CHANNELS, &dsp->channels))
    
        printf("Failed to set dsp format!\\n");
        return -1;
    
 
    if (-1 == ioctl(dsp->audio_fd, SNDCTL_DSP_SPEED, &dsp->speed))
    
        printf("Failed to set dsp format!\\n");
        return -1;
        
    return 0;

 
int play_pcm(struct audio_dsp* dsp, unsigned char *buf, int size)

    if (dsp->audio_fd == -1)
    
        printf("Invalid audio dsp id!\\n");
        return -1;
    
 
    if (-1 == write(dsp->audio_fd, buf, size))
    
        printf("Failed to write audio dsp!\\n");
        return -1;
    
 
    return 0;

/* audio renderer code end */
 
/* video renderer code*/
#include <linux/fb.h>
#include <sys/mman.h>
 
#define FB_DEVICE "/dev/fb0"
 
enum pic_format

    eYUV_420_Planer,
;
struct video_fb

    int video_fd;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    unsigned char *fbp;
    AVFrame *frameRGB;
    struct 
    
        int x;
        int y;
     video_pos;
;
 
int open_video(struct video_fb *fb, int x, int y)

    int screensize;
    fb->video_fd = open(FB_DEVICE, O_WRONLY);
    if (fb->video_fd == -1) return -1;
 
    if (ioctl(fb->video_fd, FBIOGET_FSCREENINFO, &fb->finfo)) return -2;
    if (ioctl(fb->video_fd, FBIOGET_VSCREENINFO, &fb->vinfo)) return -2;
 
 
    printf("video device: resolution %dx%d, %dbpp\\n", fb->vinfo.xres, fb->vinfo.yres, fb->vinfo.bits_per_pixel);
    screensize = fb->vinfo.xres * fb->vinfo.yres * fb->vinfo.bits_per_pixel / 8;
    fb->fbp = (unsigned char *) mmap(0, screensize, PROT_READ|PROT_WRITE, MAP_SHARED, fb->video_fd, 0);
    if (fb->fbp == -1) return -3;
 
    if (x >= fb->vinfo.xres || y >= fb->vinfo.yres)
    
        return -4;
    
    else
    
        fb->video_pos.x = x;
        fb->video_pos.y = y;
    
 
    fb->frameRGB = avcodec_alloc_frame();
    if (!fb->frameRGB) return -5;
 
    return 0;

 
/* only 420P supported now */
int show_picture(struct video_fb *fb, AVFrame *frame, int width, int height, enum pic_format format)

    struct SwsContext *sws;
    int i;
    unsigned char *dest;
    unsigned char *src;
 
    if (fb->video_fd == -1) return -1;
    if ((fb->video_pos.x >= fb->vinfo.xres) || (fb->video_pos.y >= fb->vinfo.yres)) return -2;
 
    if (fb->video_pos.x + width > fb->vinfo.xres)
    
        width = fb->vinfo.xres - fb->video_pos.x;
    
    if (fb->video_pos.y + height > fb->vinfo.yres)
    
        height = fb->vinfo.yres - fb->video_pos.y;
    
 
    if (format == PIX_FMT_YUV420P)
    
        sws = sws_getContext(width, height, format, width, height, PIX_FMT_RGB32, SWS_FAST_BILINEAR, NULL, NULL, NULL);
        if (sws == 0)
        
            return -3;
        
        if (sws_scale(sws, frame->data, frame->linesize, 0, height, fb->frameRGB->data, fb->frameRGB->linesize))
        
            return -3;
        
 
        dest = fb->fbp + (fb->video_pos.x+fb->vinfo.xoffset) * (fb->vinfo.bits_per_pixel/8) +(fb->video_pos.y+fb->vinfo.yoffset) * fb->finfo.line_length;
        for (i = 0; i < height; i++)
        
            memcpy(dest, src, width*4);
            src += fb->frameRGB->linesize[0];
            dest += fb->finfo.line_length;
        
    
    return 0;

 
void close_video(struct video_fb *fb)

    if (fb->video_fd != -1) 
    
        munmap(fb->fbp, fb->vinfo.xres * fb->vinfo.yres * fb->vinfo.bits_per_pixel / 8);
        close(fb->video_fd);
        fb->video_fd = -1;
    

/* video renderer code end */
 
int main(int argc, char **argv)

    AVFormatContext* pCtx = 0;
    AVCodecContext *pCodecCtx = 0;
    AVCodec *pCodec = 0;
    AVPacket packet;
    AVFrame *pFrame = 0;
    FILE *fpo1 = NULL;
    FILE *fpo2 = NULL;
    int nframe;
    int err;
    int got_picture;
    int picwidth, picheight, linesize;
    unsigned char *pBuf;
    int i;
    int64_t timestamp;
    struct options opt;
    int usefo = 0;
    struct audio_dsp dsp;
    struct video_fb fb;
    int dusecs;
    float usecs1 = 0;
    float usecs2 = 0;
    struct timeval elapsed1, elapsed2;
    int decoded = 0;
 
    av_register_all();
 
    av_log_set_callback(log_callback);
    av_log_set_level(50);
 
    if (parse_options(&opt, argc, argv) < 0 || (strlen(opt.finput) == 0))
    
        show_help(argv[0]);
        return 0;
    
 
 
    err = avformat_open_input(&pCtx, opt.finput, 0, 0);
    if (err < 0)
    
        printf("\\n->(avformat_open_input)\\tERROR:\\t%d\\n", err);
        goto fail;
    
    err = avformat_find_stream_info(pCtx, 0);
    if (err < 0)
    
        printf("\\n->(avformat_find_stream_info)\\tERROR:\\t%d\\n", err);
        goto fail;
    
    if (opt.streamId < 0)
    
        av_dump_format(pCtx, 0, pCtx->filename, 0);
        goto fail;
    
    else
    
        printf("\\n extra data in Stream %d (%dB):", opt.streamId, pCtx->streams[opt.streamId]->codec->extradata_size);
        for (i = 0; i < pCtx->streams[opt.streamId]->codec->extradata_size; i++)
        
            if (i%16 == 0) printf("\\n");
            printf("%2x  ", pCtx->streams[opt.streamId]->codec->extradata[i]);
        
    
    /* try opening output files */
    if (strlen(opt.foutput1) && strlen(opt.foutput2))
    
        fpo1 = fopen(opt.foutput1, "wb");
        fpo2 = fopen(opt.foutput2, "wb");
        if (!fpo1 || !fpo2)
        
            printf("\\n->error opening output files\\n");
            goto fail;
        
        usefo = 1;
    
    else
    
        usefo = 0;
    
 
    if (opt.streamId >= pCtx->nb_streams)
    
        printf("\\n->StreamId\\tERROR\\n");
        goto fail;
    
 
    if (opt.lstart > 0)
    
        err = av_seek_frame(pCtx, opt.streamId, opt.lstart, AVSEEK_FLAG_ANY);
        if (err < 0)
        
            printf("\\n->(av_seek_frame)\\tERROR:\\t%d\\n", err);
            goto fail;
        
    
 
    /* for decoder configuration */
    if (!opt.nodec)
    
        /* prepare codec */
        pCodecCtx = pCtx->streams[opt.streamId]->codec;
 
        if (opt.thread_count <= 16 && opt.thread_count > 0 )
        
            pCodecCtx->thread_count = opt.thread_count;
            pCodecCtx->thread_type = FF_THREAD_FRAME;
        
        pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
        if (!pCodec)
        
            printf("\\n->can not find codec!\\n");
            goto fail;
        
        err = avcodec_open2(pCodecCtx, pCodec, 0);
        if (err < 0)
        
            printf("\\n->(avcodec_open)\\tERROR:\\t%d\\n", err);
            goto fail;
        
        pFrame = avcodec_alloc_frame();        
 
        /* prepare device */
        if (opt.bplay)
        
            /* audio devices */
            dsp.audio_fd = open(OSS_DEVICE, O_WRONLY);
            if (dsp.audio_fd == -1)
            
                printf("\\n-> can not open audio device\\n");
                goto fail;
            
            dsp.channels = pCodecCtx->channels;
            dsp.speed = pCodecCtx->sample_rate;
            dsp.format = map_formats(pCodecCtx->sample_fmt);
            if (set_audio(&dsp) < 0)
            
                printf("\\n-> can not set audio device\\n");
                goto fail;
            
            /* video devices */
            if (open_video(&fb, 0, 0) != 0)
            
                printf("\\n-> can not open video device\\n");
                goto fail;
            
        
    
 
    nframe = 0;
    while(nframe < opt.frames || opt.frames == -1)
    
        gettimeofday(&elapsed1, NULL);
        err = av_read_frame(pCtx, &packet);
        if (err < 0)
        
            printf("\\n->(av_read_frame)\\tERROR:\\t%d\\n", err);
            break;
        
        gettimeofday(&elapsed2, NULL);
        dusecs = (elapsed2.tv_sec - elapsed1.tv_sec)*1000000 + (elapsed2.tv_usec - elapsed1.tv_usec);
        usecs2 += dusecs;        
        timestamp = av_rescale_q(packet.dts, pCtx->streams[packet.stream_index]->time_base, (AVRational)1, AV_TIME_BASE);
        printf("\\nFrame No %5d stream#%d\\tsize %6dB, timestamp:%6lld, dts:%6lld, pts:%6lld, ", nframe++, packet.stream_index, packet.size, 
               timestamp, packet.dts, packet.pts);
 
        if (packet.stream_index == opt.streamId)
        
#if 0 
            for (i = 0; i < 16; /*packet.size;*/ i++)
            
                if (i%16 == 0) printf("\\n pktdata: ");
                printf("%2x  ", packet.data[i]);
            
            printf("\\n");
#endif            
            if (usefo)
            
                fwrite(packet.data, packet.size, 1, fpo1);
                fflush(fpo1);
            
 
            if (pCtx->streams[opt.streamId]->codec->codec_type == AVMEDIA_TYPE_VIDEO && !opt.nodec)
            
                picheight = pCtx->streams[opt.streamId]->codec->height;
                picwidth = pCtx->streams[opt.streamId]->codec->width;
 
                gettimeofday(&elapsed1, NULL); 
                avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
                decoded++;
                gettimeofday(&elapsed2, NULL);
                dusecs = (elapsed2.tv_sec - elapsed1.tv_sec)*1000000 + (elapsed2.tv_usec - elapsed1.tv_usec);
                usecs1 += dusecs;
 
                if (got_picture)
                
                    printf("[Video: type %d, ref %d, pts %lld, pkt_pts %lld, pkt_dts %lld]", 
                            pFrame->pict_type, pFrame->reference, pFrame->pts, pFrame->pkt_pts, pFrame->pkt_dts);
 
                    if (pCtx->streams[opt.streamId]->codec->pix_fmt == PIX_FMT_YUV420P)
                    
                        if (usefo)
                        
                            linesize = pFrame->linesize[0];
                            pBuf = pFrame->data[0];
                            for (i = 0; i < picheight; i++)
                            
                                fwrite(pBuf, picwidth, 1, fpo2);
                                pBuf += linesize;
                            
 
                            linesize = pFrame->linesize[1];
                            pBuf = pFrame->data[1];
                            for (i = 0; i < picheight/2; i++)
                            
                                fwrite(pBuf, picwidth/2, 1, fpo2);
                                pBuf += linesize;
                                       
 
                            linesize = pFrame->linesize[2];
                            pBuf = pFrame->data[2];
                            for (i = 0; i < picheight/2; i++)
                            
                                fwrite(pBuf, picwidth/2, 1, fpo2);
                                pBuf += linesize;
                              
                            fflush(fpo2);
                         
 
                        if (opt.bplay)
                        
                            /* show picture */
                            show_picture(&fb, pFrame, picheight, picwidth, PIX_FMT_YUV420P);
                        
                    
                
                av_free_packet(&packet);
            
            else if (pCtx->streams[opt.streamId]->codec->codec_type == AVMEDIA_TYPE_AUDIO && !opt.nodec)
            
                int got;
 
                gettimeofday(&elapsed1, NULL);
                avcodec_decode_audio4(pCodecCtx, pFrame, &got, &packet);
                decoded++;
                gettimeofday(&elapsed2, NULL);
                dusecs = (elapsed2.tv_sec - elapsed1.tv_sec)*1000000 + (elapsed2.tv_usec - elapsed1.tv_usec);
                usecs1 += dusecs;
 
                if (got)
                
                    printf("[Audio: %5dB raw data, decoding time: %d]", pFrame->linesize[0], dusecs);
                    if (usefo)
                    
                        fwrite(pFrame->data[0],  pFrame->linesize[0], 1, fpo2);
                        fflush(fpo2);
                    
                    if (opt.bplay)
                    
                        play_pcm(&dsp, pFrame->data[0],  pFrame->linesize[0]);
                    
                
            
        
      
 
    if (!opt.nodec && pCodecCtx)
    
        avcodec_close(pCodecCtx);
    
 
    printf("\\n%d frames parsed, average %.2f us per frame\\n", nframe, usecs2/nframe);
    printf("%d frames decoded, average %.2f us per frame\\n", decoded, usecs1/decoded);
 
fail:
    if (pCtx)
    
        avformat_close_input(&pCtx);
    
    if (fpo1)
    
        fclose(fpo1);
    
    if (fpo2)
    
        fclose(fpo2);
    
    if (!pFrame)
    
        av_free(pFrame);
    
    if (!usefo && (dsp.audio_fd != -1))
    
        close(dsp.audio_fd);
    
    if (!usefo && (fb.video_fd != -1))
    
        close_video(&fb);
    
    return 0;

这一小段代码可以实现的功能包括:

  • 打开一个多媒体文件并获取基本的媒体信息。

  • 获取编码器句柄。

  • 根据给定的时间标签进行一个跳转。

  • 读取数据帧。

  • 解码音频帧或者视频帧。

  • 关闭多媒体文件。

这些功能足以支持一个功能强大的多媒体播放器,因为最复杂的解复用、解码、数据分析过程已经在FFMpeg内部实现了,需要关注的仅剩同步问题。

用户接口

数据结构

基本概念

编解码器、数据帧、媒体流和容器是数字媒体处理系统的四个基本概念。

首先需要统一术语:

  • 容器/文件(Conainer/File):即特定格式的多媒体文件。

  • 媒体流(Stream):指时间轴上的一段连续数据,如一段声音数据,一段视频数据或一段字幕数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。

  • 数据帧/数据包(Frame/Packet):通常,一个媒体流由大量的数据帧组成,对于压缩数据,帧对应着编解码器的最小处理单元。通常,分属于不同媒体流的数据帧交错复用于容器之中,参见交错

  • 编解码器:编解码器以帧为单位实现压缩数据和原始数据之间的相互转换。

在FFMPEG中,使用AVFormatContext、AVStream、AVCodecContext、AVCodec及AVPacket等结构来抽象这些基本要素,它们的关系如下图所示:

AVCodecContext

这是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,如下列出了部分比较重要的域:

typedef struct AVCodecContext 
 
    ......
 
    /**
     * some codecs need / can use extradata like Huffman tables.
     * mjpeg: Huffman tables
     * rv10: additional flags
     * mpeg4: global headers (they can be in the bitstream or here)
     * The allocated memory should be FF_INPUT_BUFFER_PADDING_SIZE bytes larger
     * than extradata_size to avoid prolems if it is read with the bitstream reader.
     * The bytewise contents of extradata must not depend on the architecture or CPU endianness.
     * - encoding: Set/allocated/freed by libavcodec.
     * - decoding: Set/allocated/freed by user.
     */
    uint8_t *extradata;
    int extradata_size;
    /**
     * This is the fundamental unit of time (in seconds) in terms
     * of which frame timestamps are represented. For fixed-fps content,
     * timebase should be 1/framerate and timestamp increments should be
     * identically 1.
     * - encoding: MUST be set by user.
     * - decoding: Set by libavcodec.
     */
    AVRational time_base;
 
    /* video only */
    /**
     * picture width / height.
     * - encoding: MUST be set by user.
     * - decoding: Set by libavcodec.
     * Note: For compatibility it is possible to set this instead of
     * coded_width/height before decoding.
     */
    int width, height;
 
    ......
 
    /* audio only */
    int sample_rate; ///< samples per second
    int channels;    ///< number of audio channels
 
    /**
     * audio sample format
     * - encoding: Set by user.
     * - decoding: Set by libavcodec.
     */
    enum SampleFormat sample_fmt;  ///< sample format
 
    /* The following data should not be initialized. */
    /**
     * Samples per packet, initialized when calling 'init'.
     */
    int frame_size;
    int frame_number;   ///< audio or video frame number
 
    ......
 
    char codec_name[32];
    enum AVMediaType codec_type; /* see AVMEDIA_TYPE_xxx */
    enum CodecID codec_id; /* see CODEC_ID_xxx */
 
    /**
     * fourcc (LSB first, so "ABCD" -> ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A').
     * This is used to work around some encoder bugs.
     * A demuxer should set this to what is stored in the field used to identify the codec.
     * If there are multiple such fields in a container then the demuxer should choose the one
     * which maximizes the information about the used codec.
     * If the codec tag field in a container is larger then 32 bits then the demuxer should
     * remap the longer ID to 32 bits with a table or other structure. Alternatively a new
     * extra_codec_tag + size could be added but for this a clear advantage must be demonstrated
     * first.
     * - encoding: Set by user, if not then the default based on codec_id will be used.
     * - decoding: Set by user, will be converted to uppercase by libavcodec during init.
     */
    unsigned int codec_tag;            
 
    ......
 
    /**
     * Size of the frame reordering buffer in the decoder.
     * For MPEG-2 it is 1 IPB or 0 low delay IP.
     * - encoding: Set by libavcodec.
     * - decoding: Set by libavcodec.
     */
    int has_b_frames;
 
    /**
     * number of bytes per packet if constant and known or 0
     * Used by some WAV based audio codecs.
     */
    int block_align;
 
    ......
 
    /**
     * bits per sample/pixel from the demuxer (needed for huffyuv).
     * - encoding: Set by libavcodec.
     * - decoding: Set by user.
     */
     int bits_per_coded_sample;  
 
     ......
 
 AVCodecContext;

如果是单纯使用libavcodec,这部分信息需要调用者进行初始化;如果是使用整个FFMPEG库,这部分信息在调用avformat_open_input和avformat_find_stream_info的过程中根据文件的头信息及媒体流内的头部信息完成初始化。其中几个主要域的释义如下:

  1. extradata/extradata_size:这个buffer中存放了解码器可能会用到的额外信息,在av_read_frame中填充。一般来说,首先,某种具体格式的demuxer在读取格式头信息的时候会填充extradata,其次,如果demuxer没有做这个事情,比如可能在头部压根儿就没有相关的编解码信息,则相应的parser会继续从已经解复用出来的媒体流中继续寻找。在没有找到任何额外信息的情况下,这个buffer指针为空。

  2. time_base:编解码器的时间基准,实际上就是视频的帧率(或场率)。

  3. width/height:视频的宽和高。

  4. sample_rate/channels:音频的采样率和信道数目。

  5. sample_fmt: 音频的原始采样格式。

  6. codec_name/codec_type/codec_id/codec_tag:编解码器的信息。

AVStream

该结构体描述一个媒体流,定义如下:

typedef struct AVStream 
    int index;    /**< stream index in AVFormatContext */
    int id;       /**< format-specific stream ID */
    AVCodecContext *codec; /**< codec context */
    /**
     * Real base framerate of the stream.
     * This is the lowest framerate with which all timestamps can be
     * represented accurately (it is the least common multiple of all
     * framerates in the stream). Note, this value is just a guess!
     * For example, if the time base is 1/90000 and all frames have either
     * approximately 3600 or 1800 timer ticks, then r_frame_rate will be 50/1.
     */
    AVRational r_frame_rate;
 
    ......
 
    /**
     * This is the fundamental unit of time (in seconds) in terms
     * of which frame timestamps are represented. For fixed-fps content,
     * time base should be 1/framerate and timestamp increments should be 1.
     */
    AVRational time_base;
 
    ......
 
    /**
     * Decoding: pts of the first frame of the stream, in stream time base.
     * Only set this if you are absolutely 100% sure that the value you set
     * it to really is the pts of the first frame.
     * This may be undefined (AV_NOPTS_VALUE).
     * @note The ASF header does NOT contain a correct start_time the ASF
     * demuxer must NOT set this.
     */
    int64_t start_time;
    /**
     * Decoding: duration of the stream, in stream time base.
     * If a source file does not specify a duration, but does specify
     * a bitrate, this value will be estimated from bitrate and file size.
     */
    int64_t duration;
 
#if LIBAVFORMAT_VERSION_INT < (53<<16)
    char language[4]; /** ISO 639-2/B 3-letter language code (empty string if undefined) */
#endif
 
    /* av_read_frame() support */
    enum AVStreamParseType need_parsing;
    struct AVCodecParserContext *parser;
 
    ......
 
    /* av_seek_frame() support */
    AVIndexEntry *index_entries; /**< Only used if the format does not
                                    support seeking natively. */
    int nb_index_entries;
    unsigned int index_entries_allocated_size;
 
    int64_t nb_frames;                 ///< number of frames in this stream if known or 0
 
    ......
 
    /**
     * Average framerate
     */
    AVRational avg_frame_rate;
    ......
 AVStream;

主要域的释义如下,其中大部分域的值可以由avformat_open_input根据文件头的信息确定,缺少的信息需要通过调用avformat_find_stream_info读帧及软解码进一步获取:

  1. index/id:index对应流的索引,这个数字是自动生成的,根据index可以从AVFormatContext::streams表中索引到该流;而id则是流的标识,依赖于具体的容器格式。比如对于MPEG TS格式,id就是pid。

  2. time_base:流的时间基准,是一个实数,该流中媒体数据的pts和dts都将以这个时间基准为粒度。通常,使用av_rescale/av_rescale_q可以实现不同时间基准的转换。

  3. start_time:流的起始时间,以流的时间基准为单位,通常是该流中第一个帧的pts。

  4. duration:流的总时间,以流的时间基准为单位。

  5. need_parsing:对该流parsing过程的控制域。

  6. nb_frames:流内的帧数目。

  7. r_frame_rate/framerate/avg_frame_rate:帧率相关。

  8. codec:指向该流对应的AVCodecContext结构,调用avformat_open_input时生成。

  9. parser:指向该流对应的AVCodecParserContext结构,调用avformat_find_stream_info时生成。。

AVFormatContext

这个结构体描述了一个媒体文件或媒体流的构成和基本信息,定义如下:

typedef struct AVFormatContext 
    const AVClass *av_class; /**< Set by avformat_alloc_context. */
    /* Can only be iformat or oformat, not both at the same time. */
    struct AVInputFormat *iformat;
    struct AVOutputFormat *oformat;
    void *priv_data;
    ByteIOContext *pb;
    unsigned int nb_streams;
    AVStream *streams[MAX_STREAMS];
    char filename[1024]; /**< input or output filename */
    /* stream info */
    int64_t timestamp;
#if LIBAVFORMAT_VERSION_INT < (53<<16)
    char title[512];
    char author[512];
    char copyright[512];
    char comment[512];
    char album[512];
    int year;  /**< ID3 year, 0 if none */
    int track; /**< track number, 0 if none */
    char genre[32]; /**< ID3 genre */
#endif
 
    int ctx_flags; /**< Format-specific flags, see AVFMTCTX_xx */
    /* private data for pts handling (do not modify directly). */
    /** This buffer is only needed when packets were already buffered but
       not decoded, for example to get the codec parameters in MPEG
       streams. */
    struct AVPacketList *packet_buffer;
 
    /** Decoding: position of the first frame of the component, in
       AV_TIME_BASE fractional seconds. NEVER set this value directly:
       It is deduced from the AVStream values.  */
    int64_t start_time;
    /** Decoding: duration of the stream, in AV_TIME_BASE fractional
       seconds. Only set this value if you know none of the individual stream
       durations and also dont set any of them. This is deduced from the
       AVStream values if not set.  */
    int64_t duration;
    /** decoding: total file size, 0 if unknown */
    int64_t file_size;
    /** Decoding: total stream bitrate in bit/s, 0 if not
       available. Never set it directly if the file_size and the
       duration are known as FFmpeg can compute it automatically. */
    int bit_rate;
 
    /* av_read_frame() support */
    AVStream *cur_st;
#if LIBAVFORMAT_VERSION_INT < (53<<16)
    const uint8_t *cur_ptr_deprecated;
    int cur_len_deprecated;
    AVPacket cur_pkt_deprecated;
#endif
 
    /* av_seek_frame() support */
    int64_t data_offset; /** offset of the first packet */
    int index_built;
 
    int mux_rate;
    unsigned int packet_size;
    int preload;
    int max_delay;
 
#define AVFMT_NOOUTPUTLOOP -1
#define AVFMT_INFINITEOUTPUTLOOP 0
    /** number of times to loop output in formats that support it */
    int loop_output;
 
    int flags;
#define AVFMT_FLAG_GENPTS       0x0001 ///< Generate missing pts even if it requires parsing future frames.
#define AVFMT_FLAG_IGNIDX       0x0002 ///< Ignore index.
#define AVFMT_FLAG_NONBLOCK     0x0004 ///< Do not block when reading packets from input.
#define AVFMT_FLAG_IGNDTS       0x0008 ///< Ignore DTS on frames that contain both DTS & PTS
#define AVFMT_FLAG_NOFILLIN     0x0010 ///< Do not infer any values from other values, just return what is stored in the container
#define AVFMT_FLAG_NOPARSE      0x0020 ///< Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled
#define AVFMT_FLAG_RTP_HINT     0x0040 ///< Add RTP hinting to the output file
 
    int loop_input;
    /** decoding: size of data to probe; encoding: unused. */
    unsigned int probesize;
 
    /**
     * Maximum time (in AV_TIME_BASE units) during which the input should
     * be analyzed in avformat_find_stream_info().
     */
    int max_analyze_duration;
 
    const uint8_t *key;
    int keylen;
 
    unsigned int nb_programs;
    AVProgram **programs;
 
    /**
     * Forced video codec_id.
     * Demuxing: Set by user.
     */
    enum CodecID video_codec_id;
    /**
     * Forced audio codec_id.
     * Demuxing: Set by user.
     */
    enum CodecID audio_codec_id;
    /**
     * Forced subtitle codec_id.
     * Demuxing: Set by user.
     */
    enum CodecID subtitle_codec_id;
 
    /**
     * Maximum amount of memory in bytes to use for the index of each stream.
     * If the index exceeds this size, entries will be discarded as
     * needed to maintain a smaller size. This can lead to slower or less
     * accurate seeking (depends on demuxer).
     * Demuxers for which a full in-memory index is mandatory will ignore
     * this.
     * muxing  : unused
     * demuxing: set by user
     */
    unsigned int max_index_size;
 
    /**
     * Maximum amount of memory in bytes to use for buffering frames
     * obtained from realtime capture devices.
     */
    unsigned int max_picture_buffer;
 
    unsigned int nb_chapters;
    AVChapter **chapters;
 
    /**
     * Flags to enable debugging.
     */
    int debug;
#define FF_FDEBUG_TS        0x0001
 
    /**
     * Raw packets from the demuxer, prior to parsing and decoding.
     * This buffer is used for buffering packets until the codec can
     * be identified, as parsing cannot be done without knowing the
     * codec.
     */
    struct AVPacketList *raw_packet_buffer;
    struct AVPacketList *raw_packet_buffer_end;
 
    struct AVPacketList *packet_buffer_end;
 
    AVMetadata *metadata;
 
    /**
     * Remaining size available for raw_packet_buffer, in bytes.
     * NOT PART OF PUBLIC API
     */
#define RAW_PACKET_BUFFER_SIZE 2500000
    int raw_packet_buffer_remaining_size;
 
    /**
     * Start time of the stream in real world time, in microseconds
     * since the unix epoch (00:00 1st January 1970). That is, pts=0
     * in the stream was captured at this real world time.
     * - encoding: Set by user.
     * - decoding: Unused.
     */
    int64_t start_time_realtime;
 AVFormatContext;

这是FFMpeg中最为基本的一个结构,是其他所有结构的根,是一个多媒体文件或流的根本抽象。其中:

  • nb_streams和streams所表示的AVStream结构指针数组包含了所有内嵌媒体流的描述;

  • iformat和oformat指向对应的demuxer和muxer指针;

  • pb则指向一个控制底层数据读写的ByteIOContext结构。

  • start_time和duration是从streams数组的各个AVStream中推断出的多媒体文件的起始时间和长度,以微妙为单位。

通常,这个结构由avformat_open_input在内部创建并以缺省值初始化部分成员。但是,如果调用者希望自己创建该结构,则需要显式为该结构的一些成员置缺省值——如果没有缺省值的话,会导致之后的动作产生异常。以下成员需要被关注:

  • probesize

  • mux_rate

  • packet_size

  • flags

  • max_analyze_duration

  • key

  • max_index_size

  • max_picture_buffer

  • max_delay

AVPacket

AVPacket定义在avcodec.h中,如下:

typedef struct AVPacket 
    /**
     * Presentation timestamp in AVStream->time_base units; the time at which
     * the decompressed packet will be presented to the user.
     * Can be AV_NOPTS_VALUE if it is not stored in the file.
     * pts MUST be larger or equal to dts as presentation cannot happen before
     * decompression, unless one wants to view hex dumps. Some formats misuse
     * the terms dts and pts/cts to mean something different. Such timestamps
     * must be converted to true pts/dts before they are stored in AVPacket.
     */
    int64_t pts;
    /**
     * Decompression timestamp in AVStream->time_base units; the time at which
     * the packet is decompressed.
     * Can be AV_NOPTS_VALUE if it is not stored in the file.
     */
    int64_t dts;
    uint8_t *data;
    int   size;
    int   stream_index;
    int   flags;
    /**
     * Duration of this packet in AVStream->time_base units, 0 if unknown.
     * Equals next_pts - this_pts in presentation order.
     */
    int   duration;
    void  (*destruct)(struct AVPacket *);
    void  *priv;
    int64_t pos;                            ///< byte position in stream, -1 if unknown
 
    /**
     * Time difference in AVStream->time_base units from the pts of this
     * packet to the point at which the output from the decoder has converged
     * independent from the availability of previous frames. That is, the
     * frames are virtually identical no matter if decoding started from
     * the very first frame or from this keyframe.
     * Is AV_NOPTS_VALUE if unknown.
     * This field is not the display duration of the current packet.
     *
     * The purpose of this field is to allow seeking in streams that have no
     * keyframes in the conventional sense. It corresponds to the
     * recovery point SEI in H.264 and match_time_delta in NUT. It is also
     * essential for some types of subtitle streams to ensure that all
     * subtitles are correctly displayed after seeking.
     */
    int64_t convergence_duration;
 AVPacket;

FFMPEG使用AVPacket来暂存媒体数据包及附加信息(解码时间戳、显示时间戳、时长等),这样的媒体数据包所承载的往往不是原始格式的音视频数据,而是以某种方式编码后的数据,编码信息由对应的媒体流结构AVStream给出。AVPacket包含以下数据域:

  • dts表示解码时间戳,pts表示显示时间戳,它们的单位是所属媒体流的时间基准。

  • stream_index给出所属媒体流的索引;

  • data为数据缓冲区指针,size为长度;

  • duration为数据的时长,也是以所属媒体流的时间基准为单位;

  • pos表示该数据在媒体流中的字节偏移量;

  • destruct为用于释放数据缓冲区的函数指针;

  • flags为标志域,其中,最低为置1表示该数据是一个关键帧。

AVPacket结构本身只是个容器,它使用data成员引用实际的数据缓冲区。这个缓冲区的管理方式有两种,其一是通过调用av_new_packet直接创建缓冲区,其二是引用已经存在的缓冲区。缓冲区的释放通过调用av_free_packet实现,其内部实现也采用了两种不同的释放方式,第一种方式是调用AVPacket的destruct函数,这个destruct函数可能是缺省的av_destruct_packet,对应av_new_packet或av_dup_packet创建的缓冲区,也可能是某个自定义的释放函数,表示缓冲区的提供者希望使用者在结束缓冲区的时候按照提供者期望的方式将其释放,第二种方式是仅仅将data和size的值清0,这种情况下往往是引用了一个已经存在的缓冲区,AVPacket的destruct指针为空。

在使用AVPacket时,对于缓冲区的提供者,必须注意通过设置destruct函数指针指定正确的释放方式,如果缓冲区提供者打算自己释放缓冲区,则切记将destruct置空;而对于缓冲区的使用者,务必在使用结束时调用av_free_packet释放缓冲区(虽然释放操作可能只是一个假动作)。如果某个使用者打算较长时间内占用一个AVPacket——比如不打算在函数返回之前释放它——最好调用av_dup_packet进行缓冲区的克隆,将其转化为自有分配的缓冲区,以免对缓冲区的不当占用造成异常错误。av_dup_packet会为destruct指针为空的AVPacket新建一个缓冲区,然后将原缓冲区的数据拷贝至新缓冲区,置data的值为新缓冲区的地址,同时设destruct指针为av_destruct_packet。

上述媒体结构可以通过FFMPEG提供的av_dump_format方法直观展示出来,以下例子以一个MPEG-TS文件为输入,其展示结果为:

Input #0, mpegts, from '/Videos/suite/ts/H.264_High_L3.1_720x480_23.976fps_AAC-LC.ts':
  Duration: 00:01:43.29, start: 599.958300, bitrate: 20934 kb/s
  Program 1 
    Stream #0.0[0x1011]: Video: h264 (High), yuv420p, 720x480 [PAR 32:27 DAR 16:9], 23.98 fps, 23.98 tbr, 90k tbn, 47.95 tbc
    Stream #0.1[0x1100]: Audio: aac, 48000 Hz, stereo, s16, 159 kb/s

从中可以找到媒体的格式、路径、时长、开始时间、全局比特率。此外,列出了媒体包含的一个节目,由两个媒体流组成,第一个媒体流是视频流,id为0x1011;第二个为音频流,id为0x1100。视频流是h264的High Profile编码,色彩空间为420p,大小为720×480,平均帧率23.98,参考帧率23.98,流时间基准是90000,编码的时间基准为47.95;音频流是aac编码,48kHz的采样,立体声,每个样点16比特,流的比特率是159kbps。

时间信息

时间信息用于实现多媒体同步。

同步的目的在于展示多媒体信息时,能够保持媒体对象之间固有的时间关系。同步有两类,一类是流内同步,其主要任务是保证单个媒体流内的时间关系,以满足感知要求,如按照规定的帧率播放一段视频;另一类是流间同步,主要任务是保证不同媒体流之间的时间关系,如音频和视频之间的关系(lipsync)。

对于固定速率的媒体,如固定帧率的视频或固定比特率的音频,可以将时间信息(帧率或比特率)置于文件首部(header),如AVI的hdrl List、MP4的moov box,还有一种相对复杂的方案是将时间信息嵌入媒体流的内部,如MPEG TS和Real video,这种方案可以处理变速率的媒体,亦可有效避免同步过程中的时间漂移。

FFMPEG会为每一个数据包打上时间标签,以更有效地支持上层应用的同步机制。时间标签有两种,一种是DTS,称为解码时间标签,另一种是PTS,称为显示时间标签。对于声音来说 ,这两个时间标签是相同的,但对于某些视频编码格式,由于采用了双向预测技术,会造成DTS和PTS的不一致。

无双向预测帧的情况:

图像类型: I   P   P   P   P   P   P ...  I   P   P
DTS:     0   1   2   3   4   5   6...  100 101 102
PTS:     0   1   2   3   4   5   6...  100 101 102

有双向预测帧的情况:

图像类型: I   P   B   B   P   B   B ...  I   P   B
DTS:     0   1   2   3   4   5   6 ...  100 101 102
PTS:     0   3   1   2   6   4   5 ...  100 104 102

对于存在双向预测帧的情况,通常要求解码器对图像重排序,以保证输出的图像顺序为显示顺序:

解码器输入:I   P   B   B   P   B   B
 (DTS)     0   1   2   3   4   5   6  
 (PTS)     0   3   1   2   6   4   5
解码器输出:X   I   B   B   P   B   B   P
 (PTS)     X   0   1   2   3   4   5   6

时间信息的获取:

通过调用avformat_find_stream_info,多媒体应用可以从AVFormatContext对象中拿到媒体文件的时间信息:主要是总时间长度和开始时间,此外还有与时间信息相关的比特率和文件大小。其中时间信息的单位是AV_TIME_BASE:微秒。

typedef struct AVFormatContext 
 
    ......
 
    /** Decoding: position of the first frame of the component, in
       AV_TIME_BASE fractional seconds. NEVER set this value directly:
       It is deduced from the AVStream values.  */
    int64_t start_time;
    /** Decoding: duration of the stream, in AV_TIME_BASE fractional
       seconds. Only set this value if you know none of the individual stream
       durations and also dont set any of them. This is deduced from the
       AVStream values if not set.  */
    int64_t duration;
    /** decoding: total file size, 0 if unknown */
    int64_t file_size;
    /** Decoding: total stream bitrate in bit/s, 0 if not
       available. Never set it directly if the file_size and the
       duration are known as FFmpeg can compute it automatically. */
    int bit_rate;
 
    ......
 
 AVFormatContext;

以上4个成员变量都是只读的,基于FFMpeg的中间件需要将其封装到某个接口中,如:

LONG GetDuratioin(IntfX*);
LONG GetStartTime(IntfX*);
LONG GetFileSize(IntfX*);
LONG GetBitRate(IntfX*);

APIs

FFMpeg的API大部分以0作为成功返回值而一个负数作为错误码。

读系列

读系列API的主要功能是根据某个指定的源获取媒体数据包,这个源可以是一个本地文件、一个RTSP或HTTP源、一个摄像头驱动或者其它。

avformat_open_input

int avformat_open_input(AVFormatContext **ic_ptr, const char *filename, AVInputFormat *fmt, AVDictionary **options);

avformat_open_input完成两个任务:

  1. 打开一个文件或URL,基于字节流的底层输入模块得到初始化。

  2. 解析多媒体文件或多媒体流的头信息,创建AV

    以上是关于Vite 完整版详解的主要内容,如果未能解决你的问题,请参考以下文章

    详解介绍Selenium常用API的使用--Java语言(完整版)

    2022版完整版web前端学习路线图(超详细自学路线)

    HTML常用标记(完整版)

    疯狂HTML5 CSS 3 JavaScript讲义 (李刚) 高清PDF扫描版 279M完整版下载

    疯狂HTML5 CSS 3 JavaScript讲义 (李刚) 高清PDF扫描版 279M完整版下载

    FFMPEG详解(完整版)