编码

编码流程图
解码流程图

视频编码

  1. 初始化编码器并设置参数
  2. 初始化 AVPacket 和 AVFrame ,设置参数
  3. 读取视频文件,进行编码
  4. 释放内存,结束
    编码流程
    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
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    #include <QDebug>
    extern "C"
    {
    #include <libavcodec/avcodec.h>
    }
    const char* inFileName = "input.yuv";
    const char* outFileName = "output.h264";
    int encode(AVCodecContext* codecContent, AVPacket* packet, AVFrame* frame, FILE* outFile)
    {
    //编码
    int ret = avcodec_send_frame(codecContent, frame);
    if (ret < 0)
    {
    qDebug() << "Error sending a frame for encoding";
    return -1;
    }

    while (ret == 0)
    {
    ret = avcodec_receive_packet(codecContent, packet);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
    return 0;
    }
    else if (ret < 0) {
    qDebug() <<"Error encoding video frame";
    return -1;
    }
    if (ret == 0)
    {
    fwrite(packet->data, 1, packet->size, outFile);
    }
    }
    }
    int main(int argc, char* argv[])
    {
    int ret = 0;
    const AVCodec* codec = nullptr;
    AVCodecContext* codecContent = nullptr;
    AVPacket* packet = nullptr;
    AVFrame* frame = nullptr;

    FILE* inFile = nullptr;
    FILE* outFile = nullptr;

    //查找指定编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (codec == nullptr)
    {
    qDebug() <<"could not find h264 encoder!";
    return -1;
    }

    //申请编码器上下文
    codecContent = avcodec_alloc_context3(codec);
    if (codecContent == nullptr)
    {
    qDebug() <<"could not alloc h264 content!";
    return -1;
    }

    //必设参数
    codecContent->width = 1280;
    codecContent->height = 720;
    codecContent->time_base = AVRational{ 1, 25 };
    codecContent->pix_fmt = AV_PIX_FMT_YUV420P;
    codecContent->framerate = AVRational{ 25, 1 };
    //初始化编码器上下文
    ret = avcodec_open2(codecContent, codec, NULL);
    if (ret < 0) {
    qDebug() << "Could not open codec";
    exit(1);
    }
    packet = av_packet_alloc();
    if (packet == nullptr)
    {
    qDebug() << "alloc packet error";
    return -1;
    }
    frame = av_frame_alloc();
    if (packet == nullptr)
    {
    qDebug() <<"alloc frame error";
    return -1;
    }
    //必设参数
    frame->width = codecContent->width;
    frame->height = codecContent->height;
    frame->format = codecContent->pix_fmt;
    //申请视频数据存储空间
    ret = av_frame_get_buffer(frame, 0);
    if (ret)
    {
    qDebug() <<"alloc frame buffer error!";
    return -1;
    }
    inFile = fopen(inFileName, "rb");
    if (inFile == nullptr)
    {
    qDebug() <<"error to open file";
    return -1;
    }
    outFile = fopen(outFileName, "wb");
    if (inFile == nullptr)
    {
    qDebug() <<"error to open file";
    return -1;
    }
    while (!feof(inFile))
    {
    ret = av_frame_is_writable(frame);
    if (ret < 0)
    {
    ret = av_frame_make_writable(frame);
    }
    fread(frame->data[0], 1, frame->width * frame->height, inFile); //y
    fread(frame->data[1], 1, frame->width * frame->height / 4, inFile); //u
    fread(frame->data[2], 1, frame->width * frame->height / 4, inFile); //v
    qDebug() << "encode frame";
    encode(codecContent, packet, frame, outFile);

    }
    encode(codecContent, packet, nullptr, outFile);
    qDebug() <<"encode finish";
    av_packet_free(&packet);
    av_frame_free(&frame);
    avcodec_free_context(&codecContent);
    fclose(inFile);
    fclose(outFile);
    return 0;
    }

音频编码

  1. 初始化编码器并设置参数
  2. 创建输出上下文,写入 AAC 文件头,初始化音频重采样
  3. 初始化 AVPacket 和 AVFrame,设置参数
  4. 读取视频文件,进行重采样,在进行编码
  5. 写入文件尾,释放内存,结束
    编码流程
    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
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    #include <QDebug>
    extern "C"
    {
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libswresample/swresample.h"
    }

    int main(){
    char inputfile[] = "C:\\Users\\edoYun\\Videos\\output.pcm";
    char outputfile[] = "audio.aac";
    const AVCodec* avCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    if(!avCodec){
    qDebug() << "avcodec_find_encoder failed";
    return -1;
    }
    AVCodecContext* avCodecContext = avcodec_alloc_context3(avCodec);
    if(!avCodecContext){
    qDebug() << "avcodec_alloc_context3 failed";
    return -1;
    }
    int ret=0;
    avCodecContext->sample_rate = 44100;
    avCodecContext->channels = 2;
    avCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
    avCodecContext->bit_rate = 64000;
    avCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
    ret = avcodec_open2(avCodecContext, avCodec,NULL);
    if(ret<0){
    qDebug() << "avcodec_open2 failed";
    return -1;
    }
    AVFormatContext* avFormatContext = NULL;
    avformat_alloc_output_context2(&avFormatContext, NULL, NULL, outputfile);
    if(!avFormatContext){
    qDebug() << "avformat_alloc_output_context2 failed";
    return -1;
    }
    AVStream* st = avformat_new_stream(avFormatContext, NULL);
    st->codecpar->codec_tag = 0;
    avcodec_parameters_from_context(st->codecpar, avCodecContext);
    ret = avio_open(&avFormatContext->pb, outputfile, AVIO_FLAG_WRITE);
    if(ret<0){
    qDebug() << "avio_open failed";
    return -1;
    }
    avformat_write_header(avFormatContext, NULL);

    SwrContext* swrContext = NULL;
    // 设置参数, 1. 上下文 2.输出声道布局 4.输出采样率, 5.输入声道布局 6.输入样本格式 7.输入采样率 8.配音 9.日志
    swrContext = swr_alloc_set_opts(swrContext, avCodecContext->channel_layout, avCodecContext->sample_fmt, avCodecContext->sample_rate,
    AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100,
    0, 0);
    if(!swrContext){
    qDebug() << "swr_alloc_set_opts failed";
    return -1;
    }
    // 初始化
    ret = swr_init(swrContext);
    if(ret<0){
    qDebug() << "swr_init failed";
    return -1;
    }

    AVFrame* avFrame = av_frame_alloc();
    avFrame->format = AV_SAMPLE_FMT_FLTP;
    avFrame->channels=2;
    avFrame->channel_layout = AV_CH_LAYOUT_STEREO;
    avFrame->nb_samples=1024;
    ret = av_frame_get_buffer(avFrame, 0);
    if(ret<0){
    qDebug() << "av_frame_get_buffer failed";
    return -1;
    }
    int readSize = avFrame->nb_samples*2*2; //双声道,float 16位
    char* pcms = new char[readSize];
    FILE* fp = fopen(inputfile, "rb");
    for(;;){
    AVPacket pkt;
    av_init_packet(&pkt);
    int len = fread(pcms,1,readSize, fp);
    if(len <=0){
    break;
    }else{
    const uint8_t* data[1];
    data[0] = (uint8_t*)pcms;
    len = swr_convert(swrContext, avFrame->data, avFrame->nb_samples, data, avFrame->nb_samples);
    if(len <=0){
    qDebug() << "swr_convert failed";
    break;
    }
    ret = avcodec_send_frame(avCodecContext, avFrame);
    if(ret < 0){
    qDebug() << "avcodec_send_frame failed";
    continue;
    }
    ret = avcodec_receive_packet(avCodecContext, &pkt);
    if(ret == 0){
    av_interleaved_write_frame(avFormatContext, &pkt);
    }
    }
    }
    av_write_trailer(avFormatContext);
    fclose(fp);
    avio_close(avFormatContext->pb);
    avcodec_close(avCodecContext);
    avcodec_free_context(&avCodecContext);
    avformat_free_context(avFormatContext);
    return 0;
    }

解码

音频解码

解码流程

注:在 FFmpeg 中有两种表示 PCM 数据包的模式,分别是 planar 和 packed 模式。
其中 packed 又叫交错模式,而 planar 又叫平面模式,所谓交错或平面就是不同声道的声音信号排列储存的方式,例如对于一个双声道的 PCM 数据来说,

1
2
3
4
5
6
7
用packed模式表示是这样子的:
// 我们用L表示左声道数据,用R表示右声道数据
LRLRLRLRLRLRLRLR

而用planer模式表示的话,则是这样子的:
// 我们用L表示左声道数据,用R表示右声道数据
LLLLLLLL RRRRRRRR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在FFmpeg中,packed模式的格式有:
AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
AV_SAMPLE_FMT_S16, ///< signed 16 bits
AV_SAMPLE_FMT_S32, ///< signed 32 bits
AV_SAMPLE_FMT_FLT, ///< float
AV_SAMPLE_FMT_DBL, ///< double
它的数据只存在于AVFrame的data[0]中。
而planer模式一般是FFmpeg内部储存音频所使用的模式,例如通过一般planar模式的后面都有字母P标识,planar模式的格式有:
AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
AV_SAMPLE_FMT_FLTP, ///< float, planar
AV_SAMPLE_FMT_DBLP, ///< double, planar
AV_SAMPLE_FMT_S64, ///< signed 64 bits
AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
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
83
84
85
86
87
88
89
90
91
#include <QDebug>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}

int main()
{
const char inFileName[] = "output.aac";
const char outFileName[] = "test.pcm";
FILE *file=fopen(outFileName,"wb+");
if(!file){
qDebug() << "Cannot open output file";
return -1;
}

AVFormatContext *fmtCtx =avformat_alloc_context();
AVCodecContext *codecCtx = NULL;
AVPacket *pkt=av_packet_alloc();
AVFrame *frame = av_frame_alloc();

int aStreamIndex = -1;

do{

if(avformat_open_input(&fmtCtx,inFileName,NULL,NULL)<0){
qDebug() << "Cannot open input file";
return -1;
}
if(avformat_find_stream_info(fmtCtx,NULL)<0){
qDebug() << "Cannot find any stream in file";
return -1;
}

for(size_t i=0;i<fmtCtx->nb_streams;i++){
if(fmtCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
aStreamIndex=(int)i;
break;
}
}
if(aStreamIndex==-1){
qDebug() << "Cannot find audio stream";
return -1;
}

AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;
const AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
if(!codec){
qDebug() << "Cannot find any codec for audio";
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_parameters_to_context(codecCtx,aCodecPara)<0){
qDebug() << "Cannot alloc codec context";
return -1;
}
codecCtx->pkt_timebase = fmtCtx->streams[aStreamIndex]->time_base;

if(avcodec_open2(codecCtx,codec,NULL)<0){
qDebug() << "Cannot open audio codec";
return -1;
}

while(av_read_frame(fmtCtx,pkt)>=0){
if(pkt->stream_index==aStreamIndex){
if(avcodec_send_packet(codecCtx,pkt)>=0){
while(avcodec_receive_frame(codecCtx,frame)>=0){
if(av_sample_fmt_is_planar(codecCtx->sample_fmt)){
int numBytes =av_get_bytes_per_sample(codecCtx->sample_fmt);
//pcm播放时是LRLRLR格式,所以要交错保存数据
for(int i=0;i<frame->nb_samples;i++){
for(int ch=0;ch<codecCtx->channels;ch++){
fwrite((char*)frame->data[ch]+numBytes*i,1,numBytes,file);
}
}
}
}
}
}
av_packet_unref(pkt);
}
}while(0);
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_close(codecCtx);
avcodec_free_context(&codecCtx);
avformat_free_context(fmtCtx);
fclose(file);
return 0;
}

视频解码

解码流程

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}

int main(int argc, char *argv[]) {
const char *inputFileName = "C:\\Users\\edoYun\\Videos\\output.h264";
const char *outputFileName = "out.yuv";

// 打开输入文件
AVFormatContext *formatCtx = avformat_alloc_context();
if (avformat_open_input(&formatCtx, inputFileName, NULL, NULL) != 0) {
qDebug() << "Error: could not open input file";
return -1;
}

// 获取流信息
if (avformat_find_stream_info(formatCtx, NULL) < 0) {
qDebug() << "Error: could not find stream information";
avformat_close_input(&formatCtx);
return -1;
}

// 查找视频流
int videoStream = -1;
for (int i = 0; i < formatCtx->nb_streams; i++) {
if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}

if (videoStream == -1) {
qDebug() << "Error: could not find video stream";
avformat_close_input(&formatCtx);
return -1;
}

// 获取视频解码器
AVCodecParameters *codecParams = formatCtx->streams[videoStream]->codecpar;
const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id);
if (!codec) {
qDebug() << "Error: unsupported codec";
avformat_close_input(&formatCtx);
return -1;
}

AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCtx, codecParams);
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
qDebug() << "Error: could not open codec";
avformat_close_input(&formatCtx);
return -1;
}

// 打开输出文件
FILE *outputFile = fopen(outputFileName, "wb");
if (!outputFile) {
qDebug() << "Error: could not open output file";
avcodec_close(codecCtx);
avformat_close_input(&formatCtx);
return -1;
}

// 解码并输出 YUV420 文件
AVFrame *frame = av_frame_alloc();
AVPacket packet;
av_init_packet(&packet);
int ret = 0;

while (av_read_frame(formatCtx, &packet) >= 0) {
if (packet.stream_index == videoStream) {
ret = avcodec_send_packet(codecCtx, &packet);
if (ret < 0) {
qDebug() << "Error: sending packet to decoder";
break;
}

while (ret >= 0) {
ret = avcodec_receive_frame(codecCtx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0) {
qDebug() << "Error: receiving frame from decoder";
break;
}

// 将解码后的 YUV420 数据写入文件
for (int i = 0; i < frame->height; i++) {
fwrite(frame->data[0] + i * frame->linesize[0], 1, frame->width, outputFile);
}
for (int i = 0; i < frame->height / 2; i++) {
fwrite(frame->data[1] + i * frame->linesize[1], 1, frame->width / 2, outputFile);
}
for (int i = 0; i < frame->height / 2; i++) {
fwrite(frame->data[2] + i * frame->linesize[2], 1, frame->width / 2, outputFile);
}
}
}
av_packet_unref(&packet);
}

// 释放资源
fclose(outputFile);
av_frame_free(&frame);
avcodec_close(codecCtx);
avformat_close_input(&formatCtx);

qDebug() << "Finished decoding and writing YUV420 file";
}

其他

6

AVFrame 是未压缩的图像或音频样本,AVPacket 是编码后的数据包。

PTS 是应该展示的时间,DTS 是解码所需的时间;有帧重排时二者可能不同。time_base 是一个时间刻度的长度:time_base=1/30 时 PTS=60 表示 2 秒;音频若以 1/48000 为刻度,PTS=96000 也表示 2 秒。必须换到同一单位再比较。

GOP 是一段有参考关系的画面序列。关键帧提供恢复解码的重要入口,常见 P 帧会依赖参考帧。IDR 还承担刷新参考关系的作用,不能把所有“标记为关键”的帧都不加区别地当作万能恢复点。
https://blog.csdn.net/wkd_007/article/details/134966687
https://www.cnblogs.com/samirchen/p/7071824.html