ffmpeg学习

FFmpeg

2019-09-26  本文已影响0人  simplehych

从开发小白到音视频专家 七牛云

本文在 Mac 系统下操作

安装

下载

git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg

配置

./configure --prefix=/usr/local/Cellar/ffmpeg --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265 --enable-filter=delogo --enable-debug --disable-optimizations --enable-libspeex --enable-videotoolbox --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --cc=clang --host-cflags= --host-ldflags=

报错进行安装

Q: nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.

A: brew install yasm

Q:ERROR: libfdk_aac not found

A: brew install fdk-aac

编译安装

make && make install 

Q:mkdir: /user/local/ffmpeg/lib: Permission denied
make: *** [install-libavdevice-static] Error 1

A: 修改--prefix=/usr/local/Cellar/ffmpeg路径,之前是/usr/local/ffmpeg

Q: speex not found using pkg-config / x265 not found using pkg-config
A1: 强制指定

./configure --prefix=/usr/local/Cellar/ffmpeg --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265 --enable-filter=delogo --enable-debug --disable-optimizations --enable-libspeex --enable-videotoolbox --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --cc=clang --host-cflags= --host-ldflags=\
--extra-ldflags="-L/usr/local/Cellar/speex/1.2.0/lib -L/usr/local/Cellar/x265/3.0/lib" \
--extra-cflags="-I/usr/local/Cellar/speex/1.2.0/include -I/usr/local/Cellar/x265/3.0/include"

亲测结果为:speex强制指定有效,x265无效仍报错

A2: 如果没有安装x265,先安装brew install x265;如果已经安装,pkg-config --list-all 查看所有关联的包是否有x265,如果没有重新安装x265 brew reinstall x265。检查一下环境变量的配置 PKG_CONFIG_PATH

编译

gcc/clang -g -O2 -o test test.c -I ... -L ... -lxxx
-g:输出文件中的调试信息
-O:对输出文件做指令优化,-O2编译器优化,-O1不优化
-o:输出文件
-I:指定头文件位置
-L:指定库文件的位置
-l:指定使用哪个库

编译

vi add.h // 头文件声明
#ifndef __MY_LIBRARY__
#define __MY_LIBRARY__
int add(int a, int b);
#endif

vi add.c
#ifndef __MY_LIBRARY__
#define __MY_LIBRARY__
int add(int a, int b){
        return (a+b);
}
#endif //__MY_LIBRARAY__

clang -g -c add.c  // -c 编译生成 add.o
libtool -static -o libmylib.a add.o // 输出生成libmylib.a第三方库

vi test_lib.c
#include <stdio.h> // 尖括号指定位置
#include "add.h" //引入第三方库,双引号,优先在本地目录搜索
int main(int argc, char* argv[])
{
        printf("add=%d\n",add(1,3));
        return 0;
}

clang -g -o test_lib test_lib.c -I . -L . -lmylib
./test_lib // 执行程序

调试

命令 gdb lldb
设置断点 breakpoint b b
运行程序 run r r
单步执行 next n n
跳入函数 step s s
挑出函数 finish finish
打印内容print p p
继续执行完 continue c c
查看断点 break list break list
退出 quit quit
查看指针 x/6d(s) xxxx x/6d(s) xxxx

test_lib.dSYM/Contents/Resources/DWARF

调试信息:指令地址、对应源代码及行号

dwarfdump test_lib

代码结构

描述
libavcodec 编码器的实现
libavformate 实现在流协议,容器格式及其本 IO 访问
libavutil 包括了 hash 器,解码器和各种工具函数
libavfilter 提供各种音视频过滤器
libavdevice 提供了访问捕获设备和回访设备的接口
libswresample 实现了混音和重采样
libswscale 实现了色彩转换和缩放功能

日志系统

步骤

include <libav/log.h>
ac_log_set_level(AV_LOG_DEBUG) 设置阈值
av_log(NULL, AV_LOG_INFO, "%s \n", op)

日志级别

AV_LOG_ERROR
AV_LOG_WARNING
AV_LOG_INFO
AV_LOG_DEBUG

文件的删除和重命名

avpriv_io_delete()
avpriv_io_move(src, dst)

// 设置 ffmpeg的 pkgconfig的环境变量 PKG_CONFIG_PATH
// export PKG_CONFIG_PATH=/usr/local/Cellar/ffmpeg/lib/pkgconfig 命令行单独使用只本次使用有效

pkg-config --cflags --libs libavformat
-I/usr/local/Cellar/ffmpeg/include -L/usr/local/Cellar/ffmpeg/lib -lavformat

clang -g -o ffmpeg_del ffmpeg_file.c `pkg-config --cflags --libs libavformat`

操作目录重要函数

avio_open_dir()
avio_read_dir()
avio_close_dir()

AVIODirContext
操作目录的上下文

AVIODirEntry
目录项,用于存放文件名,文件大小等属性信息

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>

int main(int argc,char* argv[])
{
        int ret;

        AVIODirContext *ctx = NULL;
        AVIODirEntry *entry = NULL;

        av_log_set_level(AV_LOG_INFO);

        ret = avio_open_dir(&ctx, "./", NULL);
        if(ret < 0){
                av_log(NULL, AV_LOG_ERROR,"Cant open dir: %s\n", av_err2str(ret));
                goto __fail;
        }

        while(1){
                ret = avio_read_dir(ctx, &entry);
                if(ret < 0){
                        av_log(NULL, AV_LOG_ERROR, "Cant read dir: %s \n", av_err2str(ret));
                        goto __fail;
                }
                if(!entry){
                        break;
                }
                av_log(NULL,AV_LOG_INFO, "%12"PRId64" %s \n", entry->size, entry -> name);
                avio_free_directory_entry(&entry);
        }

        __fail:
        avio_close_dir(&ctx);
        return 0;
}

多媒体文件的基本概念

几个重要的结构体

AVFormatContext
AVStream
AVPacket

操作流数据的基本步骤

解复用 -> 获取流 -> 读数据包 ->释放资源

实战打印音视频信息

#include <libavutil/log.h>
#include <libavformat/avformat.h>

int main(int argc, char* argv[])
{
    AVFormatContext *fmt_ctx = NULL;
    int ret = 0;

    av_log_set_level(AV_LOG_INFO);

    av_register_all();

    ret = avformat_open_input(&fmt_ctx, "./test.mp4", NULL, NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "Can't open file: %s \n", av_err2str(ret));
        return -1;
    }

    av_dump_format(fmt_ctx, 0, "./test.mp4", 0); // 第四个参数 0/1 输入/输出

    avformat_close_input(&fmt_ctx);

    return 0;
}
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './test.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    creation_time   : 2019-04-16T01:08:00.000000Z
  Duration: 00:00:15.30, bitrate: N/A
    Stream #0:0(und): Audio: aac (mp4a / 0x6134706D), 44100 Hz, 2 channels, 2 kb/s (default)
    Metadata:
      creation_time   : 2019-04-16T01:08:00.000000Z
    Stream #0:1(und): Video: h264 (avc1 / 0x31637661), none(bt709), 1920x1080, 5736 kb/s, 30 fps, 30 tbr, 3k tbn (default)
    Metadata:
      creation_time   : 2019-04-16T01:08:00.000000Z
      encoder         : JVT/AVC Coding

实战抽取音频数据

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>

int main(int argc, char* argv[])
{
    int ret;
    int audio_index;
    int len;
    char* src = NULL;
    char* dst = NULL;

    AVPacket pkt;
    AVFormatContext *fmt_ctx = NULL;

    // 日志级别
    av_log_set_level(AV_LOG_INFO);

    // 注册所有的编解码器和协议 register all format and codec
    av_register_all();

    // 1. read two params from console
    if(argc < 3){
        av_log(NULL, AV_LOG_ERROR, "the count of params should be more that three!\n");
        return -1;
    }

    src = argv[1];
    dst = argv[2];

    if(!src || !dst){
        av_log(NULL, AV_LOG_ERROR, "src or dst is null!\n");
        return -1;
    }


    ret = avformat_open_input(&fmt_ctx, src, NULL, NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "Can't open file: %s \n", av_err2str(ret));
        return -1;
    }

    // 输出 meta 信息
    av_dump_format(fmt_ctx, 0, src, 0);

    // 创建输出文件
    FILE* dst_fd = fopen(dst, "wb");
    if(!dst_fd){
        av_log(NULL, AV_LOG_ERROR, "Can't open out file!\n");
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 2. get Stream
    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "Cant't find the best stream!\n");
        avformat_close_input(&fmt_ctx);
        fclose(dst_fd);
        return -1;
    }

    audio_index = ret;

    // 初始化包
    av_init_packet(&pkt);

    // 获取每个包
    while(av_read_frame(fmt_ctx, &pkt) >= 0){

        if(pkt.stream_index == audio_index){

            // 3. write audio data to aac file
            len = fwrite(pkt.data, 1, pkt.size, dst_fd);
            if(len != pkt.size){
                av_log(NULL, AV_LOG_WARNING, "waning, length of data is not equal size of pkt!\n");
            }
        }
        av_packet_unref(&pkt);
    }

    avformat_close_input(&fmt_ctx);

    if(dst_fd){
        fclose(dst_fd);
    }

    return 0;
}

抽取视频数据

开始 + 视频帧的长度

开始 + 特征码

  1. 一般需要一个 SPS / PPS,每次解码都有相同的

  2. 当分辨率发生变化时,需要更新 SPS / PPS,一般有多个。

  3. 直播流里,网络的原因丢数据,切换分辨率丢帧,在每一帧添加 SPS / PPS,不会增加流量,只有几个字节很小,没有任何负担

切换分辨率等变换 SPS / PPS,数据丢包在每一帧添加 SPS / PPS

从编码器 codec 的扩展数据 extradata 中##获取 SPS / PPS##,不是在正常的存储数据包,


将 MP4 转成 FLV 格式

从 MP4 截取一段视频

FFmpeg 中级开发

H264解码
H264编码
AAC 解码
AAC 编码

FFmpeg H264解码

添加头文件

常用数据结构

结构体内存的分配与释放

解码步骤

FFmpeg H264编码

H264编码流程

SDL

介绍

SDL编译与安装

SDL使用步骤

SDL渲染窗口

SDL 事件基本原理

SDL事件种类
SDL事件处理

纹理渲染

纹理

SDL渲染基本原理

SDL渲染基本原理

SDL纹理相关的API

SDL渲染相关API

YUV 视频播放器

创建线程

SDL_更新纹理

SDL播放音频

播放音频基本流程

播放音频基本流程

播放音频的基本原则

SDL 音频 API

最简单的播放器

多线程与锁

为啥要用多线程

线程的互斥与同步

锁与信号量

SDL 线程的创建

SDL锁

SDL条件变量

播放器线程模型

播放器线程模型

音视频同步

时间戳

时间戳顺序

实际帧顺序:I B B P
存放帧顺序:I P B B
解码时间戳:1 4 2 3
展示时间戳:1 2 3 4

从哪获得 PTS

时间基

计算当前帧的 PTS

计算下一帧的 PTS

音视频同步方式

视频播放的基本思路

一般的做法,展示第一针视频帧之后,获取要显示的下一视频帧的 PTS,然后设置一个定时器,当定时器超时后,刷新新的视频帧,如此反复操作。

上一篇下一篇

猜你喜欢

热点阅读