1.基础知识

1.1 vim

基本命令

命令 作用 命令 作用
dd 删除一行 dw 删除一个
yy 复制一行 yw 复制一个
h 光标左移动 l 光标右移
j 光标下移动 k 光标上移
gg 跳到文件头 G 跳到文件尾部
^ 移动行首 $ 移动行尾
w 前移一位 2w 前移2位
b 后移一位 2b 后移2位
/关键字 查找关键字 %s/关键字/替换字/gc 查找替换
p 粘贴 set number 查看行号
1
2
替换使用范围
:number1,number2s/关键字/替换字/gc

多窗口

命令 作用
:split 上下划分窗口
:vsplit 左右划分窗口
control+ww 窗口切换
:close 窗口关闭
control+w+- 窗口撑开
control+w+= 窗口缩小

1.2 编译器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gcc/clang -g  -O2 -o test test.c -I... -L... -l...
--------------------------------------------------

-g:输出文件中的调试信息     -I:指定头文件
-O:对输出文件做指令优化     -L:指定库文件位置
-o:输出文件               -l:指定使用哪个库

--------------------------------------------------

预编译------->编译------>链接

1.3 调试器

命令 gdb lldb
设置断点 b b
运行程序 r r
单步执行 n n
跳入函数 s s
跳出函数 finish finish
打印内容 p p
1
2
//查看生成可执行文件的调试信息
dwarfdump dbug.dSYM/Contents/Resources/DWARF/dbug 

2. FFmpeg 结构

FFmpeg 结构

3. FFmpeg 日志功能

3.1 导入库

1
#include <libavutil/log.h>

3.2 设置日志级别

1
2
3
4
5
6
7
8
//DEBUG 以及以上级别的日志都会打印
/**
AV_LOG_ERROR
AV_LOG_WARNING
AV_LOG_INFO
AV_LOG_DEBUG
*/
av_log_set_level(AV_LOG_DEBUG);

3.3 打印

1
2
//打印AV_LOG_INFO 级别的日志
av_log(NULL,AV_LOG_INFO,"...%s\n",op);

3.4 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include<stdio.h>
#include<libavutil/log.h>
int main(int arc,char* argv[])
{
    av_log_set_level(AV_LOG_DEBUG);

    av_log(NULL,AV_LOG_INFO,"hello world!");

    return 0;
}
1
clang -g -o ffmegp_log ffmpeg_log.c -lavutil

4.文件删除与重命名

4.1 导入

1
#include <libavformat/avformat.h>

4.2 操作

1
2
3
4
//删除
avpriv_io_delete();
//移动
avpriv_io_move();

4.3 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <libavformat/avformat.h>

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

    ret = avpriv_io_move("111.txt","22.txt");

    if(ret < 0){
        av_log(NULL,AV_LOG_ERROR,"failed to move txt");
        return -1;
    }

    av_log(NULL,AV_LOG_INFO,"success to move");

    ret = avpriv_io_delete("./mytest.txt");

    if(ret < 0){
        av_log(NULL,AV_LOG_ERROR,"failed to delete file mytest.txt\n");
        return -1;
    }

    av_log(NULL,AV_LOG_INFO,"success to delete");

    return 0;
}
1
clang -g -o ffmpeg_io ffmpeg_io.c `pkg-config --libs libavformat`

5. 文件目录操作

5.1方法与结构体

1
2
3
avio_open_dir();
avio_read_dir();
avio_close_dir();
1
2
3
4
//操作目录的上下文
AVIODirContext
//目录项,用于存放文件名,文件大小等信息
AVIODirEntry

5.2 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <libavutil/log.h>
#include <libavformat/avformat.h>

int main(int argc, char* argv[])
{
    AVIODirContext *ctx = NULL;
    AVIODirEntry *entry = NULL;
    int ret;
    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);
        //entry释放
        avio_free_directory_entry(&entry);
    }

_fail:
    //目录句柄释放
    avio_close_dir(&ctx);
    return 0;
}

6. 音视频文件结构

FFmpeg 数据结构

7. 获取音视频信息

7.1 方法

1
2
3
4
5
//注册
av_register_all();
//获取/关闭
avformat_open_input()/avformat_close_input();
//打印

7.2 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <libavutil/log.h>
#include <libavformat/avformat.h>

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

    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,"cant open file: %s\n",av_err2str(ret));
        return -1;
    }

    av_dump_format(fmt_ctx,0,"./test.mp4",0);
    avformat_close_input(&fmt_ctx);

    return 0;
}
1
clang -g -o ffmpeg_mediainfo ffmpeg_mediainfo.c  `pkg-config --libs libavformat libavutil`

8. 抽取音频数据

8.1 方法

1
2
3
4
5
6
//初始化包
av_init_packet();
//找到最佳流
av_find_best_stream();
//读取帧/去掉引用
av_read_frame()/av_packet_unref()

8.2 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;         //syncword:0xfff                          高8bits
    szAdtsHeader[1] = 0xf0;         //syncword:0xfff                          低4bits
    szAdtsHeader[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
    szAdtsHeader[1] |= (0 << 1);    //Layer:0                                 2bits 
    szAdtsHeader[1] |= 1;           //protection absent:1                     1bit

    szAdtsHeader[2] = (audio_object_type - 1)<<6;            //profile:audio_object_type - 1                      2bits
    szAdtsHeader[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bits 
    szAdtsHeader[2] |= (0 << 1);                             //private bit:0                                      1bit
    szAdtsHeader[2] |= (channel_config & 0x04)>>2;           //channel configuration:channel_config               高1bit

    szAdtsHeader[3] = (channel_config & 0x03)<<6;     //channel configuration:channel_config      低2bits
    szAdtsHeader[3] |= (0 << 5);                      //original:0                               1bit
    szAdtsHeader[3] |= (0 << 4);                      //home:0                                   1bit
    szAdtsHeader[3] |= (0 << 3);                      //copyright id bit:0                       1bit  
    szAdtsHeader[3] |= (0 << 2);                      //copyright id start:0                     1bit
    szAdtsHeader[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bits

    szAdtsHeader[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bits
    szAdtsHeader[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bits
    szAdtsHeader[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
    szAdtsHeader[6] = 0xfc;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ret = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1 ,-1,NULL,0); 
...
av_init_packet(&pkt);
av_read_frame(fmt_ctx,&pkt);
while(av_read_frame(fmt_ctx,&pkt)>=0){
    if(pkt.stream_index == audio_index){
        char adts_header_buf[7];
        adts_header(adts_header_buf, pkt.size);
        fwrite(adts_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,"warnig! length of data is not equal size of pkt");
        }
    }
    av_packet_unref(&pkt);
}

9. 抽取视频数据

9.1 h264

h264有两种封装, 一种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中 一种是mp4模式,一般mp4 mkv会有,没有startcode,SPS和PPS以及其它信息被封装在container中,每一个frame前面是这个frame的长度 很多解码器只支持annexb这种模式,因此需要将mp4做转换

H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。他们的结构如下图所示。

h264

其中每个NALU之间通过startcode(起始码)进行分隔,起始码分成两种:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001

H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段

9.2 sps pps

H.264码流第一个 NALU 是 SPS(序列参数集Sequence Parameter Set) H.264码流第二个 NALU 是 PPS(图像参数集Picture Parameter Set) H.264码流第三个 NALU 是 IDR(即时解码器刷新)

H.264的SPS和PPS串,包含了初始化H.264解码器所需要的信息参数,包括编码所用的profile,level,图像的宽和高,deblock滤波器等。

9.3 解码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
static int alloc_and_copy(AVPacket *out,
                          const uint8_t *sps_pps, uint32_t sps_pps_size,
                          const uint8_t *in, uint32_t in_size)
{
    uint32_t offset         = out->size;
    uint8_t nal_header_size = offset ? 3 : 4;
    int err;

    err = av_grow_packet(out, sps_pps_size + in_size + nal_header_size);
    if (err < 0)
        return err;

    if (sps_pps)
        memcpy(out->data + offset, sps_pps, sps_pps_size);
    memcpy(out->data + sps_pps_size + nal_header_size + offset, in, in_size);
    if (!offset) {
        AV_WB32(out->data + sps_pps_size, 1);
    } else {
        (out->data + offset + sps_pps_size)[0] =
        (out->data + offset + sps_pps_size)[1] = 0;
        (out->data + offset + sps_pps_size)[2] = 1;
    }

    return 0;
}

int h264_extradata_to_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding){
    ...
    while (unit_nb--) {
        ...  
        if ((err = av_reallocp(&out, total_size + padding)) < 0)
            return err;
        memcpy(out + total_size - unit_size - 4, nalu_header, 4);
        memcpy(out + total_size - unit_size, extradata + 2, unit_size);
        extradata += 2 + unit_size;
pps:
        if (!unit_nb && !sps_done++) {
            unit_nb = *extradata++; /* number of pps unit(s) */
            if (unit_nb) {
                pps_offset = total_size;
                pps_seen = 1;
            }
        }
    }

    if (out)
        memset(out + total_size, 0, padding);
    ...
}
int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *dst_fd){
    ...
    do {
        ...
        if (unit_type == 5) {
            h264_extradata_to_annexb( fmt_ctx->streams[in->stream_index]->codec->extradata,
                                      fmt_ctx->streams[in->stream_index]->codec->extradata_size,
                                      &spspps_pkt,
                                      AV_INPUT_BUFFER_PADDING_SIZE);
            if ((ret=alloc_and_copy(out,
                               spspps_pkt.data, spspps_pkt.size,
                               buf, nal_size)) < 0)
                goto fail;
        }
        ...
        len = fwrite( out->data, 1, out->size, dst_fd);
        fflush(dst_fd);
        ...
    } while (cumul_size < buf_size);

}

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

    ...
     while(av_read_frame(fmt_ctx, &pkt) >=0 ){
        if(pkt.stream_index == video_stream_index){
            h264_mp4toannexb(fmt_ctx, &pkt, dst_fd);
        }
        av_packet_unref(&pkt);
    }
    ...
}

10. 格式转换

10.1 关键方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//获取输出上下文
avformat_alloc_output_context2()/avformat_free_context()
//生成新的流
avformat_new_stream()
//复制宽高等信息
avformat_parameters_copy()
//生成文件头
avformat_write_header()
//写帧
av_write_frame()/av_interleaved_write_frame()
//写尾
av_write_trailer()

10.2 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
...
//只提取视频、音频、字幕,将基本参数直接复制
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
    AVStream *out_stream;
    AVStream *in_stream = ifmt_ctx->streams[i];
    AVCodecParameters *in_codecpar = in_stream->codecpar;

    if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
        in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
        in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
        stream_mapping[i] = -1;
        continue;
    }

    stream_mapping[i] = stream_index++;

    out_stream = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream) {
        fprintf(stderr, "Failed allocating output stream\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
    if (ret < 0) {
        fprintf(stderr, "Failed to copy codec parameters\n");
        goto end;
    }
    out_stream->codecpar->codec_tag = 0;
}
...
//写入头部
ret = avformat_write_header(ofmt_ctx, NULL);
...
while (1) {
    AVStream *in_stream, *out_stream;

    ret = av_read_frame(ifmt_ctx, &pkt);
    ...
    //读取一包数据,找到相应的输出流
    in_stream  = ifmt_ctx->streams[pkt.stream_index];
    if (pkt.stream_index >= stream_mapping_size ||
        stream_mapping[pkt.stream_index] < 0) {
        av_packet_unref(&pkt);
        continue;
    }

    pkt.stream_index = stream_mapping[pkt.stream_index];
    out_stream = ofmt_ctx->streams[pkt.stream_index];
    log_packet(ifmt_ctx, &pkt, "in");

    /* copy packet */
    //重新计算时间基等参数
    pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
    pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
    pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
    pkt.pos = -1;
    log_packet(ofmt_ctx, &pkt, "out");
    //写入数据
    ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
    if (ret < 0) {
        fprintf(stderr, "Error muxing packet\n");
        break;
    }
    av_packet_unref(&pkt);
}
//写入尾部
av_write_trailer(ofmt_ctx);

11. 裁剪

11.1 方法

1
av_seek_frame()

11.2 时间戳概念

时间戳解释

11.3 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
...
//获取输出句柄
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
...
//复制编码器
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
    AVStream *in_stream = ifmt_ctx->streams[i];
    AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
    if (!out_stream) {
        fprintf(stderr, "Failed allocating output stream\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }
 
    ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
    if (ret < 0) {
        fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
        goto end;
    }
    out_stream->codec->codec_tag = 0;
    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
}
...
//写入头部
ret = avformat_write_header(ofmt_ctx, NULL);
...
//定位开头
ret = av_seek_frame(ifmt_ctx, -1, from_seconds*AV_TIME_BASE, AVSEEK_FLAG_ANY);
...
//循环读入数据
while(1){
    ...
    //读入一帧
    ret = av_read_frame(ifmt_ctx, &pkt);
    ...
    //判断结尾
    if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
        av_free_packet(&pkt);
        break;
    }
    ...
    //转换时间戳
    pkt.pts = av_rescale_q_rnd(pkt.pts - pts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
    pkt.dts = av_rescale_q_rnd(pkt.dts - dts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
    ...
    //写入一帧
    ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
    ...
}
...
//写入尾
av_write_trailer(ofmt_ctx);
...