FFmpeg

FFmpeg

  • 处理音视频开源框架
  • C/C++编写
  • 非常优秀的多媒体框架
  • 解码,编码,转码,复用,解复用,过滤音视频数据

音视频的广泛应用

  • 直播类:音视频会议,教育直播,娱乐/游戏直播等
  • 短视频:抖音,快手,小咖秀等
  • 网络视频:优酷,腾讯视频,爱奇艺等
  • 音视频通话:微信,QQ,Skype等
  • 视频监控
  • 人工智能:人脸识别,智能音箱等

播放器架构

在这里插入图片描述

FFmpeg常用命令

FFmpeg处理流程

  1. 首先需要一个输入文件(.mp4/flv/avi),相当于一个装有信息的盒子
  • 具有封装格式的数据
  • 有音频数据
  • 视频数据
  • 字母数据
  1. 然后我们需要打开这个盒子(demuxer)
  • 打开后盒子有
  • 编码的视频数据
  • 编码的音频数据
  1. 对压缩的数据进行解码(decoder)
  • 解码的数据与原数据还是有所差别的
  1. 对解码的数据进行处理
    • 720p->480p
    • 加滤镜
    • ……
  2. 再次编码成数据包(encoder)
  3. 封装成流行的格式(muxer)
  4. 输出数据

基本信息查询

  1. -version:显示版本
  2. -demuxers:显示可用的demuxers
  3. -muxers:显示可用的muxers
  4. -devices:显示可用的设备
  5. -codecs:显示所有编解码器
  6. -decoders:显示可用的解码器
  7. -encoders:显示可用的编码器
  8. -bsfs:显示比特流filer
  9. -formats:显示可用的格式
  10. -protocols:显示可用的协议
  11. -filters:显示可用的过滤器
  12. -pix_fmts:显示可用的像素格式
  13. -sample-fmts:显示可用的采样可是
  14. -layouts:显示channel名称
  15. -color:显示识别的颜色名称

录制命令

录制屏幕

ffmpeg -f avfoundation -i 1 -r 30 out.yuv

以上为一个录制屏幕的命令

  • 参数介绍
    • -f:指定使用使用 avfoundation库(mac系统下) 来录制
    • -i:指定输入设备
    • 1:设备索引值,1代表屏幕,0代表摄像头,等等
    • -r:指定帧率为30
    • out.yuv:采集的数据保存为yuv数据格式(一个无压缩的数据格式)
Input #0, avfoundation, from '1':
  Duration: N/A, start: 19326.762000, bitrate: N/A
    Stream #0:0: Video: rawvideo (UYVY / 0x59565955), uyvy422, 1920x1080, 1000k tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> rawvideo (native))
Press [q] to stop, [?] for help
Output #0, rawvideo, to 'out.yuv':
  Metadata:
    encoder         : Lavf58.20.100
    Stream #0:0: Video: rawvideo (UYVY / 0x59565955), uyvy422, 1920x1080, q=2-31, 995328 kb/s, 30 fps, 30 tbn, 30 tbc
    Metadata:
      encoder         : Lavc58.35.100 rawvideo

从上面Stream行可以发现:视频的像素格式是 uyvy422,大小是1920x1080

使用:ffplay -s 1920x1080 -pix_fmt uyvy422 out.yuv 播放视频

  • -s:指定屏幕大小
  • -pix_fmt:指定像素格式

ffmpeg -f avfoundation -list_devices true -i ""

  • 查找avfoundation支持的设备
[AVFoundation input device @ 0x7fbb18d03e80] AVFoundation video devices:
[AVFoundation input device @ 0x7fbb18d03e80] [0] FaceTime HD Camera (Built-in)
[AVFoundation input device @ 0x7fbb18d03e80] [1] Capture screen 0
[AVFoundation input device @ 0x7fbb18d03e80] AVFoundation audio devices:
[AVFoundation input device @ 0x7fbb18d03e80] [0] Built-in Microphone
  • 由上可知:
    • avfoundation支持的视频(video)设备
      • HD Camera,其设备索引值为 0
      • Capture Screen,其索引值为 1
    • 支持的音频(audio)
      • Build—in Microphone,其索引值为 0
录制声音

ffmpeg -f avfoundation -i :0 out.wav

  • 参数介绍
    • -f:使用的设备
    • -i:指定输入设备
    • :0:音频设备在 : (冒号)之前指定,视频设备在 冒号 之后指定

分解/复用

  • 各种个样文件格式之间的转换
  • 从一个视频中单独抽取音频或画面

ffmpeg -i out.mp4 -vcodec copy -acodec copy out.flv

  • 参数解释
    • -i:输入文件
    • -vcodec:设置视频编解码
    • copy:表示不改变有编解码
    • -acodec:设置音频编解码
    • out.flv:输出文件
Metadata:
    major_brand     : mp42
    minor_version   : 1
    compatible_brands: isommp423gp5
    creation_time   : 2018-11-02T08:07:34.000000Z
    encoder         : FormatFactory : www.pcfreetime.com
  Duration: 00:06:24.08, start: 0.000000, bitrate: 356 kb/s
    Stream #0:0(und): Video: mpeg4 (Simple Profile) (mp4v / 0x7634706D), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 228 kb/s, 14.91 fps, 14.91 tbr, 14906 tbn, 7453 tbc (default)
  • 由命令行输出可知:
    • Duration:视频长度 6分24秒,起始时间0.0秒,码率:356kb/s
    • Stream:视频格式是yuv420p,大小是1920x1080,帧率事故14.91

ffpeg -i test.mov -an -vcodec copy out.h264

这是一个只要视频,不要音频的分割命令

  • 参数介绍:
    • -i:输入资源
    • -an:autio No,不要音频资源
    • -vn:vedio No,不要视频资源
    • -vcodec:视频编码格式设置

处理原始数据命令

  • 通过 FFmpeg 提取YUV数据

ffmpeg -i input.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv

  • 参数解释:

    • -an:audio No,输出结果不包括音频
    • -c:v:对视频进行编码,用rawvideo进行编码
    • -pix_fmt:像素格式为 yuv420p
  • FFmpeg 提取PCM数据

ffmpeg -i out.mp4 -vn -ar 44100 -ac 2 -f s16le out.pcm

  • 参数解释:
    • -vn:vedio No,不要视频
    • -ar:audio read,音频采样率,指定采样率44.1k
    • -ac:audio ,声道数目,单声道/双声道/立体声等
    • -f:抽取出来的PCM数据的存储格式

ffplay -ar 44100 -ac 2 -f s16le out.pcm

  • 这里告诉ffplay播放器 应该以何种方法播放 PCM 文件

掌握这些获取原始数据的命令,可以方便进行对比
通过ffmpeg做二次开发,需要提取出其中的yuv数据,做每一帧的分析等,需要知道这些命令
例如:声道反转等,需要知道这些原始数据

剪裁与合并命令

ffmpeg -i in.mp4 -ss 00:00:00 -t 10 out.ts

  • 参数解释:
    • -ss:视频从什么时间点开始裁剪–时:分:秒
    • -t:截取点时间长度,单位为s

ffmpeg -f concat -i inputs.txt out.flv

  • 参数解释:
    • -f concat:表示对后面对文件进行拼接
    • -i inputs.txt:inputs.txt中记录对是文件列表
    • input.txt:内容为 “file filename”格式

input.txt文件

file '1.ts'
file '2.ts'
file '3.ts'

图片/视频互转命令

  • 视频转图片
    ffmpeg -i in.flv -r 1 -f image2 image-%3d.jpeg

  • 参数解释:

    • -r:指定转换图片的帧率,每秒多少张图片
    • -f:将输入文件转成什么格式,这转为image2格式
    • image-%3d.jpeg:图片名字
  • 图片转视频
    ffmpeg -i image-%3d.jpeg out.mp4

  • 参数解释:

    • -i:输入文件列表

直播相关命令

  • 推流

ffmpeg -re -i out.mp4 -c copy -f flv rtmp://server/live/streamName

  • 参数解释:

    • -re:减慢帧率速度,让它的帧率与真正的帧率保持同步
    • -i:推出去的流的多媒体文件
    • -c:音视频编辑码
    • -f:推出去的文件格式
    • rtmp的服务器地址
  • 拉流

ffmpeg -i rtmp://server/live/streamName -c copy dump.flv

  • 参数解释:
    • -i:流的资源
    • -c:音视频编码

ffmpeg -i rtmp://ivi.bupt.edu.cn/hls/cctv1hd.m3u8 -c copy out.flv

这样ffmpeg 就会从某个服务器中网本地存数据

各种滤镜命令

  • 最复杂的一类命令,因为可是实现各种各样想要达到的效果
  1. 拿到解码后的数据枕
  2. 经过滤镜程序进行过滤
  3. 将过滤后的数据帧重新编码

ffmpeg -i test.mp4 -vf crop=in_w-200:in_h-200 -c:v libx264 -c:a copy out.mp4

  • 参数解释:
    • -vf:vedio fuilter,指定视频滤镜
    • in_w:crop的参数,本身视频宽度
    • in_w-200:本身视频宽度减去200
    • in_h:本身视频的高度
    • c:v:指定使用的视频的编码器,libx264
    • c:a:指定使用的音频的编码器

初级开发内容

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

FFmpeg 日志的使用

#include <libavutil/log.h>

av_log_set_level(AV_LOG_DEBUG)

av_log(NULL, AV_LOG_INFO, "...%s\n", op);
  • 这个的函数 av_log_set_level是设置输出日志的等级,凡是比这个参数等级高的都要输出

    • AV_LOG_DEBUG是Debug级别的,是最低级的,也就意味着全部输出
    • 还有ERROR,WARNING,INFO等
  • 这个函数 av_log(NULL, AV_LOG_INFO, “…%s\n”, op)

    • 第一个参数 一般设为NULL
    • 第二个参数 设置打印日志的级别,同上
    • 第三个参数 格式化输出
  • 常用的日志级别

    • AV_LOG_ERROR 等级最高
    • AV_LOG_WARNING 等级次高
    • AV_LOG_INFO 等级次低
    • AV_LOG_DEBUG 等级最低
#include "libavutil/log.h"
#include <stdio.h>

int main(){
    set_av_log_level(AV_LOG_DEBUG);
    av_log(NULL, AV_LOG_INFO, "HelloWorld\n");
    return 0;
}

文件删除与重命名

  • avpriv_io_delete()
    • 传入参数为删除文件地址
    • 用于删除文件
  • avpriv_io_move()
    • 两个参数
      • 第一个参数 原地址
      • 第二个参数 移动后地址
    • 可以移动或者重命名
#include <libavformat/avformat.h>
#include "libavutil/log.h"
int main(){
    
    set_av_log_level(AV_LOG_DEBUG);

    int ret = avpriv_io_delete("./test.txt");
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "delete file error\n");
        return -1;
    }

    ret = avpriv_io_move("11.txt", "22.txt");
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "move file error\n");
        return -1;
    }

    return 0;
}

操作目录

  • avio_open_dir();
    • 打开一个文件夹
    • 第一个参数 AVIODirContext 结构体指针
    • 第二个参数 打开目录
    • 第三个参数 一般为NULL
  • avio_read_dir();
    • 读取这个文件夹中每个文件/文件夹的信息
  • avio_close_dir();
    • 读完之后关闭dir

重要结构体

  • AVIODirContext:操作目录的上下文
  • AVIODirEntry:目录项,用于存放文件名,文件大小信息等

当调用 open_dir 的时候,ffmepg就会生成AVIODirContext结构体
read_dir的时候,就要传入这个 AVIODirContext结构体
close_dir的时候,传入的特使AVIODirContext

使用ffmpeg实现 ls 命令

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

int main(){
    av_log_set_level(AV_LOG_INFO);
    AVIODirContext *ctx = NULL;
    AVIODirEntry *entry = NULL;
    int ret = avio_open_dic(&ctx, "./", NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "Can't open dir %s\n", av_err2str(ret));
        return -1;
    }
    while(1){
        ret = avio_read_dir(ctx, &entry);
        if(ret < 0){
             av_log(NULL, AV_LOG_ERROR, "Can't read dir %s\n", av_err2str(ret));
            return -1;
        }
        if(!entry){//读到目录最后一项
            break;
        }
        av_log(NULL, AV_LOG_INFO, "%12"PRId64" %s \n",entry->size, entry->name );//打印大小和名字
    }

    return 0;
}

多媒体文件的基本概念

  • 多媒体文件其实是个容器
    • 存放了各种类型的数据:字幕,音频,视频。。。
  • 容器里面有很多流(Stream/Track)(以前称之为轨)
  • 每种流是由不同的编码器编码的
  • 从流中读出的数据成为包
  • 在一个包中包含着一个或多个帧

几个重要的结构体

  • AVFormatContext:格式上下文,连接多个api的桥梁
  • AVStream:流/轨,用于读取流
  • AVPacket:用于截取流中的数据

执行顺序:解复用 -> 获取流 -> 读取数据包 -> 释放资源

打印 音/视 频信息

  • av_register_all()
    • 讲ffmpeg将ffmpeg中的协议,编解码库等全部注册到程序中去
    • 必须调用的
  • avformat_open_input()/avformat_cloase_input()
    • 第一个参数:AVFormatContext结构体
    • 第二个参数:要打开的文件
    • 第三个参数:打开的文件格式,如果为NULL,则默认按照传入文件的后缀名解析
    • 第四个参数:通过命令行传递参数,一般为NULL就行
    • 打开一个多媒体文件,根据文件后缀名识别多媒体格式
    • 输出一个结构体——AVFormatContext,格式上下文
  • av_dump_format()
    • 第一个参数:AVFormatContext
    • 第二个参数:流的索引值,直接填 0
    • 第三个参数:多媒体文件的名字
    • 第四个参数:输入流(0)还是输出流(1)
    • 将多媒体文件中的mate信息打印出来

#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(){
    av_log_set_level(AV_LOG_INFO);
    av_regester_all();
    AVFormatContext *fmt_context = NULL;
    int ret = avformat_open_input(&fmt_context, "./test.mp4", NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "con't open file %s\n", av_err2str(ret));//打印错误码
        return -1}
    av_dump_format(fmt_context, 0, "./test.mp4", 0);
    avformat_close_input(&fmt_context);
    return 0;
}

抽取音频数据

  • av_init_packet()

    • 初始化数据包结构体
    • 从多媒体文件中读取的每一个数据包都可以放在这里
  • av_find_best_stream()

    • 找到最好的一路流
    • 第一个参数:AVFormatContext
    • 第二个参数:流的类型,AVMEDIA_TYPE_***,ffmpeg宏定义的
    • 第三个参数:要处理的流的索引号,不知道填-1
    • 第四个参数:相关的流的索引号,不知道填-1
    • 第五个参数:流的编解码器,不知道填NULL
    • 第六个参数:flag
  • av_read_frame()

    • 将流中的数据包获取到,可以进行后续处理
    • 从流中读取相应数据包后,数据包引用计数+1
    • 第一个参数:AVFormatContext
    • 第二个参数:使用的包,AVPacket
  • av_packet_unref()

    • 数据包不用的话要引用计数-1
    • 内存管理机制
#include <libavutil/log.h>
#include <libavformat/avformat.h>

void adts_header(char *szAdtsHeader, int dataLen){
    int audio_object_type = 2;
    int sampling_frequency_index = 7;
    int channel_config = 2;
    int adtsLen = dataLen + 7;
    szAdtsHeader[0] = 0xff; 
    
    szAdtsHeader[1] = 0xf0;
    szAdtsHeader[1] |= (0<<3);
    szAdtsHeader[1] |= (0<<1);
    szAdtsHeader[1] |= 1;

    szAdtsHeader[2] = (audio_object_type - 1)<<6;
    szAdtsHeader[2] |= (sampling_frequency_index & 0x0f)<<2;
    szAdtsHeader[2] |= (0 << 1);
    szAdtsHeader[2] |= (channel_config & 0x04)>>2;

    szAdtsHeader[3] = (channel_ config & 0x03)<<6;
    szAdtsHeader[3] |= (0 << 5) ;
    szAdtsHeader[3] |= (0 << 4) ;
    szAdtsHeader[3] |= (0 << 3) ;
    szAdtsHeader[3] |= (0 << 2) ;
    szAdtsHeader[3] |= ( (adtsLen & 0x1800) >> 11) ;

    szAdtsHeader[4] = (uint8_ t)( (adtsLen & 0x7f8) >> 3) ;

    szAdtsHeader[5] = (uint8_ t)( (adtsLen & 0x7) << 5);
    szAdtsHeader[5] |= 0x1f ;

    szAdtsHeader[6] = 0xfc;
}

int main(){
    av_log_set_level(AV_LOG_INFO);
    av_regester_all();
    AVFormatContext *fmt_context = NULL;
    int ret = avformat_open_input(&fmt_context, "./test.mp4", NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "con't open file %s\n", av_err2str(ret));//打印错误码
        return -1}
    av_dump_format(fmt_context, 0, "./test.mp4", 0);
    avformat_close_input(&fmt_context);

    // 获取 想要处理的流
    int audio_index;
    audio_index = av_find_best_stream(fmt_context, AVMEDIA_TYPE_AUDIO, -1-1, NULL, 0)//如果audio_index >=0,则其代表找到是音频数据的索引值
    if(audio_index < 0){
        av_log(NULL, AV_LOG_ERROR, "con't file stream file %s\n", av_err2str(ret));//打印错误码
        return -1}
    // 打开一个可供写入的文件
    FILE* dst_fd = fopen(./test.aac”, "wb");
    if(!dst_fd){
        av_log(NULL, AV_LOG_ERROR, "Can't open out file\n");
        avformat_close_input(&fmt_context);
        return -1;
    }


    // 读取信息
    AVPacket pkt;
    av_init_packet(&pkt);
    while(av_read_frame(fmt_context, &pkt) >= 0){//  要读取所有的数据帧,
        if(audio_index == pkt.stream_index){// 如果相同则代表找到流了
            // 添加一个头,表示如何播放分离出来的audio。如果不加上头,不知道如何播放
            char adts_header_buf[7];
            adts_header(adts_header_buf, pkt.size);
            fwrite(dats_header_buf, 1, 7, dst_fd);

            // 将获取的音频数据输出到一个新文件中
            int len = fwrite(pkt.data, 1 , pkt.size, dst_fd);//写如文件
            if(len != pkt.size){
                av_log(NULL, AV_LOG_WARNING, "waning file write length data error");
            }
        }
        av_packet_unref(pkt);//需要释放引用计数,防止内存泄漏
    }
    // 关闭文件
    avformat_close_input(&fmt_context);
    if(dst_fd){
        fclose(dft_fd);
    }
    return 0;
}

抽取视频 H264 数据

  • Start Code

    • 区分一个个的视频帧,及其顺序
    • 每一帧前面都加上一个特征码(Start Code),以区分每一个帧的边界
  • SPS/PPS

    • 解码的视频参数,包括视频帧的 宽高/帧率等等,都存在SPS/PPS中
    • 理论上只要一个SPS/PPS,但是实际上需要多个SPS/PPS,因为视频播放时需要切换清晰度时,就需要更换SPS/PPS
    • 在每个关键帧的前面都会增加SPS/PPS数据包(防止直播切换清晰度时,发来的SPS/PPS包丢了而导致错误)
  • codec->extradata

    • 从codec结构的extradata中获取SPS/PPS



int main(){
    int err_code;
    char errors[1024];
    char *src_filename = NULL;
    char *dst_fileanme = NULL;

    FILE *dst_fd = NULL;

    int video_stream_index = -1;

    AVFormatContext *fmt_ctx = NULL;
    AVPacket pkt;

    av_log_set_level(AV_LOG_DEBUG);

    if(argc < 3){
        av_log(NULL, AV_LOG_DEBUG, "the count of parameters shoudler be more than three~");
        return -1;
    }
    src_filename = argv[1];
    dsf_filename = argv[2];

    if(src_filename == NULL || dsf_filename == NULL){
        av_log(NULL, AV_LOG_ERROR, "src or dts file is null
        \n");
        return -1;
    }

    av_register_all();

    dst_fd = fopen(dst_filename, "wb");
    if(!dst_fd){
        av_log(NULL, AV_LOG_ERROR, "Can't open destionation file\n");
        return -1;
    }

    if((err_code = avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)) < 0){
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "Con't open source file:%s ,%d(%s)\n", src_filename, err_code, errors);
        return -1;
    }
    av_dump_format(fmt_ctx, 0, src_filename, 0);//打印多媒体信息

    av_init_packet(&pkt);
    pkt_data = NULL;
    pkt_size = 0;

    video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if(video_stream_index < 0){
        av_log(NULL, AV_LOG_DEBUG, "Can't find %s stream in input file %s\n", av_get_media_type_string(AVMEDIA_TYPE_VIDEO), src_filename);
        return AVERROR(EINVAL);
    }

    while(av_read_frame(fmt_ctx, &pkt) >= 0){
        if(pkt.stream_index == video_stream_index){
            h264_mp4toannexb(fmt_ctx, &pkt, dst_df);//自定义函数,设置 Start Code和SPS/PPSs,懒得写。。。。
        }
        av_packet_unref(&pkt);// 处理过的包就去掉引用
    }
}

环境打死配不好,手敲要死了

感谢帮助
https://blog.csdn.net/lijjianqing/article/details/54707785
https://blog.csdn.net/zhouxj0818/article/details/52679741

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值