FFmpeg解封装的原理与实战


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

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


FFMpeg 解封装

本例子实现的是将音视频分离,例如将封装格式为 FLV、MKV、MP4、AVI 等封装格式的文件,将音频、视频分离开来。

实现的过程,可以大致用如下图表示:

image.png

  从图中可以看出大致的解封装流程

  1. 首先要对解复用器进行初始化。

  2. 其次将输入的封装格式文件给到解复用器内。

  3. 最后利用解封装对 Container 进行解封装。


根据流程可以推到出大致的代码流程

  • 首先对输入文件(Container 文件)、输出文件(Video/Audio 进行处理),方便后面的使用;

  • 其次打开输入文件,并分配 Format Context,从输入文件中得到流信息

  • 之后打开视频、音频编码器 Context,针对视频数据,分配图像 image。

  • 分配 frame 结构,初始化 packet,从输入文件中读取 frame 信息,并之后进行解码 packet。

  • 最后释放各种分配的数据信息。




ffmpeg解封装案例分析

    下面讨论如何利用ffmpeg提供的API函数进行多媒体文件的解封装(demux)过程。

    在讲解之前,我们先复习一些基本的多媒体文件知识。


  • 容器格式:不管是音频文件还是视频格式的文件,都是一个多媒体的容器,即container,比如常见的视频容器格式有avi、mp4、mkv、flv、rm/rmvb、mov、ts、vob、dat,音频容器格式有MP3、WAV、AAC、APE,FLAC等等,它容纳了视频、音频、字幕(subtitle)等一个或多个基本流数据,有的甚至一个容器中存放有多个视频、音频以及字幕。


  • 压缩格式:对视频、音频数据的基本流进行的压缩方式就是音视频的压缩格式。常见的视频压缩格式如mpeg2、mpeg4、H264、VC1、Rm/Rmvb,常见音频压缩格式如MPA、AAC、AC3、DTS。注意这里的部分名字和上面的一样,但意义不同,上面是封装格式,这里是压缩格式。为什么要压缩呢?因为不压缩的话,要存储图像或声音就需要非常多的空间,比如mpeg2压缩比能达到25:1左右,而H264甚至能达到102:1的惊人程度!


  • ES:也就是ElementaryStream,也称为基本流、组件流等称呼,就是单独的一路视频、一条音频、一个subtitle字幕或者单个的附加数据。显然常见的多媒体文件一个都有一个视频ES、音频ES,有的也含有多个视频ES和音频ES以及subtitleES。比如蓝光原版的TS一般都含有多个音轨ES和字幕ES,但不是所有有字幕都有字幕ES,可能字幕已经内嵌进视频,这样的字幕其实成了视频的一部分。


  • Demux:在播放时,需要把这些视音频以及字幕等基本流分离出来,这个过程就叫Demux,或者解封装,也称为解复用。分离出来的各个基本流(ES)分别送给视频解码器、音频解码器等解码后才能得到图像声音。:


  • Remux:当然Demux反过来把基本的音频、视频、字幕等组合成一个完整的多媒体就是Remux或者封装,也称为复用。比如很多电影网站的音视频压制的人就需要先做Demux,分离成ES,在加入必要的中文字幕和音轨后、重新封装。所有的转码工具也都必须有Remux和重新Demux的过程。复用与解复用的概念对于熟悉DVB行业的读者来说应该比较清楚。


  • PTS:也就是显示时间戳,指图像或者声音在解码后应该显示或者发声的时间点。音视频不是一解码出来就播出来,否则就乱了,性能好的解码器播放的快,差的播放的慢,并且视频和音频也对不上号。所有这些都是靠PTS来同步的。至于DTS解码时间戳在现在相对以前较大解码内存缓冲下,显得不那么重要了。


有了这些基本的多媒体知识,我们就可以继续讲解如何利用ffmpeg来进行Demux这个过程。

首先介绍一下主要的几个API函数:


intavformat_open_input(AVFormatContext **ps, const char *filename,

    AVInputFormat *fmt, AVDictionary **options);

这个函数用于打开多媒体文件,并读取相关文件头信息。


void  avformat_close_input(AVFormatContext **ps);

这个函数用于关闭上面打开的多媒体文件,释放相关资源。


int  avformat_find_stream_info(AVFormatContext *ic, AVDictionary**options);

这个函数通过注册的文件格式解析器读取文件的取各种信息,比如播放持续时间、音视频压缩格式、音轨信息、字幕信息、帧率、采样率等等。


int av_read_frame(AVFormatContext*s, AVPacket *pkt);

这个函数对于Demux过程是最重要的一个函数,它从文件中读取一帧视频、一帧或多帧音频、字幕等ES数据包,除了数据本身之外,还包括PTS、持续时间、参考帧等重要信息。


void av_free_packet(AVPacket *pkt);

这个函数用于释放ES数据包,与上面的函数成对使用。



有了这些函数和上面的基本知识,下面我们来实现一个简单的Demux框架实例

这个实例的功能是把多媒体文件中的音视频ES数据抽出来分别写入不同文件

我们为了简单,这里不处理返回错误,在实际项目中自己添加错误处理机制

本文用最简单最原始的方式把ffmpeg解封装的基本框架讲解清楚。


#include <stdio.h>

#include "libavformat/avformat.h"
static const char *media_file = "test_media.mp4";
int main(void)
{
    int i, vid_idx, aud_idx;
    FILE *fp_vides = NULL, *fp_audes = NULL;
    AVFormatContext *pFormatCtx = NULL;
    AVPacket pkt;
    av_register_all();
    avformat_open_input(&pFormatCtx, media_file, NULL, NULL);
    avformat_find_stream_info(pFormatCtx, NULL);
    fp_vides = fopen("vid_es.dat", "wb");
    fp_audes = fopen("aud_es.dat", "wb");
    
    // 1, handle stream info
    for (i=0; i<pFormatCtx->nb_streams; i++)
    {
    if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO)
        vid_idx = i;
    else if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_AUDIO)
        aud_idx = i;
    else
        ;//such as subtitile
    }
    
    while (av_read_frame(pFormatCtx, &pkt) >= 0)
    {
        // 2, handle pkt data
        if (pkt.stream_index == vid_idx)
        fwrite(pkt.data, pkt.size, 1, fp_vides);
        else if (pkt.stream_index == aud_idx)
        fwrite(pkt.data, pkt.size, 1, fp_audes);
        else
        ;// such as subtitile
        av_free_packet(&pkt);
    }
    
    fclose(fp_vides);
    fclose(fp_audes);
    avformat_close_input(&pFormatCtx);
    return 0;
}

在注释1的地方,需要处理基本流索引与音视频对应的关系和重要信息记录,这个关系会在注释2的地方用到,并且也是后续的多音轨、字幕切换的凭据,本例只处理了最简单的只有一路音视频的情况,且没有对其他信息进行记录,比如帧率、视频宽高、编码类型、时间标度、第一个PTS等等。



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