FFmpeg系列(三)—— 解封装

摘要:音视频文件 = 音频流+视频流+同步信息,音频信号和视频信号通过某种标准格式进行组合生成封装格式,如 .mp4/.avi/.flv/.mkv 等。在底层,只能解析纯视频或者音频数据,不能直接处理封装的文件。所以想将封装的视频文件分离为音频,视频和字幕等其他辅助信息等才能播放音视频文件。

1、预备知识

1.1 解封装流程

调用avformat_open_input() 函数注册解封装上下文AVFormatContext
调用av_find_best_stream() 函数获取音视频索引
调用av_packet_alloc() 为解分装后的数据包AVPacket分配空间
调用av_read_frame() 开始解封装

1.2 解封装相关函数说明

    /* 1、初始化相关函数 */
    av_register_all();            //注册所有的解封装和加封装格式,在解封装视频文件之前需要调用一次
    avformat_open_input();        //打开并解析一个封装,调用之前确保av_register_all已调用
    avformat_find_stream_info();  //查找文件格式索引
    av_find_best_stream();        //找到音频流或视频流都用这个函数,分开处理音频和视频
    avformate_close_input();      //关闭输入文件,并释放封装上下文,将ic置NULL
    av_read_frame();              //读取具体的音视频帧数据

    /* 2、AVPacket相关函数 */
    av_packet_alloc();     //AVPacket空间创建并初始化
    av_packet_clone();     //创建空间并将引用计数加1
    av_packet_ref();       //手动给引用计数加1,在此之前确保AVPacket已创建
    av_packet_unref();     //手动给引用计数减1
    av_packet_free();      //清空AVPacket对象,并减引用计数
    av_init_packet();      //默认值
    av_packet_from_data(); //自定义数据转成AVPacket包
    av_copy_packet();      //已抛弃,用av_packet_clone替代

2、完整代码

编译之前需要在官网下载FFmpeg工具,并在工程中指定头文件和库文件目录,以下代码已在VS2019下编译测试OK

#include <iostream>
extern "C" { //在官网下载的FFmpeg包
	#include "libavcodec/avcodec.h"
	#include "libavformat/avformat.h"
}
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")

static double r2d(AVRational r) //分数转浮点数
{
    return (r.den == 0) ? 0 : ((double)r.num / (double)r.den);  //需要防止分母为0
}

int main(void)
{
    const char      *path          = "1.mp4";    //视频文件路径
    char            errorMsg[1024] = { 0 };      //存放错误信息
    AVFormatContext *ic            = NULL;       //解封装上下文

    //==========1、创建解封装上下文==========
    av_register_all();          //注册所有加解封装器
    int ret = avformat_open_input(&ic, path, 0, NULL);   //0表示自动选择解封装器,opts可有可无
    if (ret != 0)   //判断是否打开成功
    {
        av_strerror(ret, errorMsg, sizeof(errorMsg) - 1); //获取错误信息
        std::cout << "open " << path << " failed! : " << errorMsg << std::endl;
        return -1;  //打开失败
    }

    //========== 2、获取流信息 ==========
    ret = avformat_find_stream_info(ic, 0); //并不是所有格式文件都需要调用(例如MP4),此处统一调用

    //========== 3、获取音视频索引 ==========
    int videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    int audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

    //========== 4、开始解封装 ==========
    AVPacket* pkt = av_packet_alloc();  //创建AVPacket并初始化
    for (;;)    //解封装,得到的是压缩后的数据
    {
        ret = av_read_frame(ic, pkt); //读取具体的音视频帧数据
        if (ret != 0)   //读取失败或达到文件末尾
        {
            std::cout << "===========================end===========================\n";
            int ms = 3000;  //3秒
            long long pos = (double)ms / 1000 * r2d(ic->streams[pkt->stream_index]->time_base);
            //将索引至于开头pos位置,用视频帧做seek,往后找关键帧
            av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
            break;
        }
        std::cout << "大小:" << pkt->size;
        std::cout << " 显示时间:" << pkt->pts;
        std::cout << " 解码时间:" << pkt->dts;
        std::cout << " " << pkt->pts * (r2d(ic->streams[pkt->stream_index]->time_base) * 1000); //将pts转换成毫秒,方便做同步
        if (pkt->stream_index == videoStream)   //视频帧
            std::cout << " 图像" << std::endl;
        else if (pkt->stream_index == audioStream)   //视频帧
            std::cout << " 音频" << std::endl;

        av_packet_unref(pkt);   //清理AVPacket中的所有空间数据,并且将data与size置0
    }

    av_packet_free(&pkt);           //清空AVPacket对象
    if (ic)                         //判断ic是否已经被释放
        avformat_close_input(&ic);  //释放封装上下文,并把ic置0

    return 0;
}

3、视频信息获取

在调用 avformat_find_stream_info() 函数之后,AVFormatContext 里已经存放里视频相关信息,可以用作其他处理,下面展示如何获取一些基本的音视频参数信息。

int printSomeInfo()
{
    int videoStream = 0; // 视频流索引
    int audioStream = 1; // 音频流索引
    int64_t totalMs = ic->duration / (AV_TIME_BASE / 1000); //获取视频总时长,单位ms
    av_dump_format(ic, 0, path, 0);                         //打印分辨率,比特率,帧率等详细信息
    std::cout << "总时长:" << totalMs << "ms" << std::endl;

    for (unsigned int i = 0; i < ic->nb_streams; ++i)   //获取所有流信息,可能包含音频,视频,字幕等流信息
    {
        AVStream* as = ic->streams[i];  //音频、视频或字幕等流信息
        if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) //音频
        {
            audioStream = i;    //音频流索引
            std::cout << "*** " << i << "、音频信息 ***" << std::endl;
            std::cout << "样本率sample_rate:" << as->codecpar->sample_rate << std::endl;
            std::cout << "通道数量channels:" << as->codecpar->channels << std::endl;
            std::cout << "样本数frame_size:" << as->codecpar->frame_size << std::endl; //一帧数据存的是单通道样本数量
            //帧率fps = sample_rate / frame_size
        }
        else if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)   //视频
        {
            videoStream = i;    //视频流索引
            std::cout << "*** " << i << "、视频信息 ***" << std::endl;
            std::cout << "宽度width:" << as->codecpar->width << std::endl;
            std::cout << "高度height:" << as->codecpar->height << std::endl;
            //注意,流媒体数据宽高可能没有,需要解码之后才能获取
            std::cout << "帧率fps:" << r2d(as->avg_frame_rate) << std::endl;//音频不存在该参数
        }
        std::cout << "样本格式format:" << as->codecpar->format << std::endl;
        std::cout << "解封装格式codec_id:" << as->codecpar->codec_id << "\n\n";
    }
}

4、结构体参数说明

🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵

/* 结构体申明 */
/******************************************************************
* AVFormatContext               //加解封装,包含封装参数和AVStream
*    AVIOContext *pb;           //文件IO上下文
*    char filename[1024];       //文件名
*    unsigned int nb_streams;   //流元素个数,视频文件包含多少个流
*    AVStream **streams;        //音频和视频信息
*    int64_t duration;          //整个媒体文件长度
*    int64_t bit_rate;          //比特率,1s文件的大小
******************************************************************/

/****************************************************************************************
* AVStream                          //每一个流(音视频流)的参数信息,所有配置信息
*   AVCodecContext *codec;          //解封装和编码没做隔离,该参数已经过时,不使用
*   AVRational time_base;           //时间基数,用分数表示,为了保障精度
*   int64_t duration;               //时间段,该参数不一定有,以AVFormatContext里的为准
*   int64_t nb_frames;              //帧数
*   AVRational avg_frame_rate;      //帧率,用分数表示
*   AVCodecParameters *codecpar;    //音视频参数,用于替代AVCodecContext
****************************************************************************************/

/****************************************************************************************
* AVCodecParameters                 //音视频参数,存放在AVStream结构体里
*    enum AVMediaType codec_type;   //编码类型,音频还是视频
*    enum AVCodecID codec_id;       //编码格式
*    uint32_t codec_tag;            //编码器标签,用不到
*    int format;                    //像素格式或音频采样格式
*    int width; int height;         //音视频宽高
*    uint64_t channel_layout;       //声道
*    int channels; int sample_rate; int frame_size; //声道数,样本率,一帧音频大小
****************************************************************************************/

/****************************************************************************************
* AVPacket                     //具体的解封装完之后的数据包,用av_read_frame()函数读取数据包
*    AVBufferRef *buf;         //指向的空间用来存储引用计数,代码中基本不用管
*    int64_t pts;              //显示时间pts * (num / den) 乘以时间基数,值可能特别大
*    int64_t dts;              //解码时间
*    uint8_t *data; int size;  //数据缓存和数据大小,用ffmpeg提供的接口声请空间
****************************************************************************************/
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值