FFmpeg音视频解码原理与实战


***【在线视频教程】***

好文章,来自【福优学苑@音视频+流媒体】

FFmpeg解码音视频流程简介

音视频编码流程简介(使用 ffmpeg)

 


  • 第一步:注册组件(编码器、解码器…)

  • 第二步:初始化封装格式上下文

  • 第三步:打开输入文件

  • 第四步:创建输出码流(设置为视频流或音频流)

  • 第五步:查找视频/音频编码器

  • 第六步:打开视频/音频编码器

  • 第七步:写入文件头信息(有些文件头信息)->一般情况下都会有

  • 第八步:循环编码视频像素数据->视频压缩数据            

  • 第九步:将编码后的视频/音频压缩数据写入文件中

  • 第十步:输入像素/采样数据读取完毕后回调函数


 


 


音频解码流程

 

image.png

 

 


视频解码

在视频解码前,先了解以下几个基本的概念:

 


编解码器(CODEC):能够进行视频和音频压缩(CO)与解压缩(DEC),是视频编解码的核心部分。


容器/多媒体文件(Container/File):没有了解视频的编解码之前,总是错误的认为平常下载的电影的文件的后缀(avi,mkv,rmvb等)就是视频的编码方式。


事实上,刚才提到的几种文件的后缀并不是视频的编码方式,只是其封装的方式。一个视频文件通常有视频数据、音频数据以及字幕等,封装的格式决定这些数据在文件中是如何的存放的,封装在一起音频、视频等数据组成的多媒体文件,也可以叫做容器(其中包含了视音频数据)。


所以,只看多媒体文件的后缀名是难以知道视音频的编码方式的。


流数据 Stream,例如视频流(Video Stream),音频流(Audio Stream)。流中的数据元素被称为帧Frame。


 


 


关键函数说明

avcodec_find_decoder:根据指定的AVCodecID查找注册的解码器。


av_parser_init:初始化AVCodecParserContext。


avcodec_alloc_context3:为AVCodecContext分配内存。


avcodec_open2:打开解码器。


av_parser_parse2:解析获得一个Packet。


avcodec_send_packet:将AVPacket压缩数据给解码器。


avcodec_receive_frame:获取到解码后的AVFrame数据。


av_get_bytes_per_sample: 获取每个sample中的字节数。


 

 


解码流程说明

AVCodecParser:用于解析输入的数据流并把它分成一帧一帧的压缩编码数据。

比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据



 


解码过程的具体说明

1. 注册

只需要调用一次,故一般放在main函数中。也可以注册某个特定的容器格式,但通常来说不需要这么做。


 


2. 打开文件

avformat_open_input该函数读取文件的头信息,并将其信息保存到AVFormatContext结构体中。


 


 


3. 获取必要的CODEC参数

够获取到足够多的关于文件、流和CODEC的信息,并将这些信息填充到AVFormatContext结构体中。


但任何一种多媒体格式(容器)提供的信息都是有限的,而且不同的多媒体制作软件对头信息的设置也不尽相同,在制作多媒体文件的时候难免会引入一些错误。也就是说,仅仅通过avformat_open_input并不能保证能够获取所需要的信息,所以一般要使用


avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)


avformat_find_stream_info主要用来获取必要的CODEC参数,设置到ic->streams[i]->codec。


 


4. 打开解码器

经过上面的步骤,已经将文件格式信息读取到了AVFormatContext中,要打开流数据相应的CODEC需要经过下面几个步骤


找到视频流 video stream


一个多媒体文件包含有多个原始流,例如 movie.mkv这个多媒体文件可能包含下面的流数据


  • 原始流 1 h.264 video

  • 原始流 2 aac audio for Chinese

  • 原始流 3 aac audio for English

  • 原始流 4 Chinese Subtitle

  • 原始流 5 English Subtitle


  


 


5. 读取数据帧并解码

已经有了相应的解码器,下面的工作就是将数据从流中读出,并解码为没有压缩的原始数据


AVPacket packet;
 
while (av_read_frame(pFormatCtx, &packet) >= 0)
{
        if (packet.stream_index == videoStream)
        {
            int frameFinished = 0;
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
            if (frameFinished)
            {
                doSomething();
            }
        }
    }


 


上述代码调用av_read_frame将数据从流中读取数据到packet中,并调用avcodec_decode_video2对读取的数据进行解码。


 

 


6. 关闭


需要关闭avformat_open_input打开的输入流,avcodec_open2打开的CODEC

    avcodec_close(pCodecCtxOrg);

    avformat_close_input(&pFormatCtx);  


 


源代码分析

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
 
 
#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
 
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
 
#ifdef __cplusplus
};
#endif
 
int openCodecContext(const AVFormatContext *pFormatCtx, int *pStreamIndex, enum AVMediaType type, AVCodecContext **ppCodecCtx)
{
    int streamIdx = -1;
    // 获取流下标
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == type)
        {
            streamIdx = i;
            break;
        }
    }
    if (streamIdx == -1)
    {
        printf("find video stream failed!\n");
        exit(-2);
    }
    // 寻找解码器
    AVCodecContext  *pCodecCtx = pFormatCtx->streams[streamIdx]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (NULL == pCodec)
    {
        printf("avcode find decoder failed!\n");
        exit(-2);
    }
    //打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("avcode open failed!\n");
        exit(-2);
    }
    *ppCodecCtx        = pCodecCtx;
    *pStreamIndex    = streamIdx;
    return 0;
}
 
//static const char *media_file = "ande_302.mp4";
int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);
 
    AVFormatContext    *pInFormatCtx    = nullptr;
    AVCodecContext  *pVideoCodecCtx    = nullptr;
    AVCodecContext  *pAudioCodecCtx    = nullptr;
    AVPacket *pPacket    = nullptr;
    AVFrame *pFrame        = nullptr;
    int ret;
    /* 支持本地文件和网络url */
    const char streamUrl[] = "ande_flv.flv";
 
    printf("-------hello,ffmpeg!--------\n");
 
    /* 1. 注册 */
    av_register_all();
 
    pInFormatCtx = avformat_alloc_context();
    /* 2. 打开流 */
    if(avformat_open_input(&pInFormatCtx, streamUrl, NULL, NULL) != 0)
    {
        printf("Couldn't open input stream.\n");
        return -1;
    }
 
    /* 3. 获取流的信息 */
    if(avformat_find_stream_info(pInFormatCtx, NULL) < 0)
    {
        printf("Couldn't find stream information.\n");
        return -1;
    }
 
    int videoStreamIdx = -1;
    int audioStreamIdx = -1;
 
 
    /* 4. 寻找并打开解码器 */
    openCodecContext(pInFormatCtx, &videoStreamIdx, AVMEDIA_TYPE_VIDEO, &pVideoCodecCtx);
    openCodecContext(pInFormatCtx, &audioStreamIdx, AVMEDIA_TYPE_AUDIO, &pAudioCodecCtx);
    pPacket    = av_packet_alloc();
    pFrame    = av_frame_alloc();
    int cnt = 10;
    while (cnt--)
    {
        /* 5. 读流数据, 未解码的数据存放于pPacket */
        ret = av_read_frame(pInFormatCtx, pPacket);
        if (ret < 0)
        {
            printf("av_read_frame error\n");
            break;
        }
 
        /* 6. 解码, 解码后的数据存放于pFrame */
        /* 视频解码 */
        if (pPacket->stream_index == videoStreamIdx)
        {
            avcodec_decode_video2(pVideoCodecCtx, pFrame, &ret, pPacket);
            if (ret == 0)
            {
                printf("video decodec error!\n");
                continue;
            }
            printf("* * * * * * video * * * * * * * * *\n");
            printf("___height: [%d]\n", pFrame->height);
            printf("____width: [%d]\n", pFrame->width);
            printf("pict_type: [%d]\n", pFrame->pict_type);
            printf("___format: [%d]\n", pFrame->format);
            ///AVSampleFormat
            printf("* * * * * * * * * * * * * * * * * * *\n\n");
        }
        /* 音频解码 */
        if (pPacket->stream_index == audioStreamIdx)
        {
            avcodec_decode_audio4(pAudioCodecCtx, pFrame, &ret, pPacket);
            if (ret < 0)
            {
                printf("audio decodec error!\n");
                continue;
            }
            printf("* * * * * * audio * * * * * * * * * *\n");
            printf("____nb_samples: [%d]\n", pFrame->nb_samples);
            printf("__samples_rate: [%d]\n", pFrame->sample_rate);
            printf("channel_layout: [%lu]\n", pFrame->channel_layout);
            printf("________format: [%d]\n", pFrame->format);
            printf("* * * * * * * * * * * * * * * * * * *\n\n");
        }
        av_packet_unref(pPacket);
    }
    av_frame_free(&pFrame);
    av_packet_free(&pPacket);
    avcodec_close(pVideoCodecCtx);
    avcodec_close(pAudioCodecCtx);
    avformat_close_input(&pInFormatCtx);
 
 
    return a.exec();
}


 


好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***