ffmpeg基础一:解封装

2023-11-15

参考:零声学院

1、解封装流程

在这里插入图片描述

2、打开文件或媒体,获取文件或媒体的媒体信息AVFormatContext

//AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体
AVFormatContext *ifmt_ctx = NULL;           
// 输入文件的信息,其中包含了打开的输入文件的很多信息,为后面解封装使用
// url: 媒体文件的路径/名字
// nb_streams: 媒体流数量
// bps: 媒体文件的码率,单位为bps
// duration: 媒体文件时长,单位微妙
// streams: 音频流或视频流,字母流,很重要,后面要对这个里面的流进行分析
AVFormatContext *ifmt_ctx = NULL;
// 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接
int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);
if (ret < 0)  //如果打开媒体文件失败,打印失败原因
{
    char buf[1024] = { 0 };
    av_strerror(ret, buf, sizeof(buf) - 1);
    printf("open %s failed:%s\n", in_filename, buf);
    goto failed;
}

使用avformat_open_input打开文件或者打开网络流,这样AVFormatContext 就会保存有文件或网络流的基本信息:比如有几个流,媒体的总时长等,后面就可以根据流的数量,单独分析音频流和视频流

3、流信息AVStream

AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流
// 从上面的文件信息结构体获取出各个流的信息
//in_stream->codecpar->codec_type: 流判断,AVMEDIA_TYPE_AUDIO,AVMEDIA_TYPE_VIDEO
//in_stream->codecpar->sample_rate:音频采样率(8K,44.1K,48K)
//in_stream->codecpar->format: 音频采样格式(AV_SAMPLE_FMT_FLTP,AV_SAMPLE_FMT_S16P)
//in_stream->codecpar->channels:音频信道数目
//in_stream->codecpar->codec_id:音频压缩编码格式(AAC,MP3)
	// AV_CODEC_ID_MP3 AV_CODEC_ID_AAC 
//in_stream->avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧
//in_stream->codecpar->codec_id:视频压缩编码格式(AV_CODEC_ID_MPEG4,AV_CODEC_ID_H264)
//in_stream->codecpar->width:视频帧宽度
//in_stream->codecpar->height:视频帧高度

通过判断AVFormatContext中有几个流,来获取AVStream ,这样就可以获取文件中的视频流和音频流,以及可能存在的字母流,并且可以从这些AVStream中获取出很多信息,后面可以根据这些信息查找编解码器,进行解码播放

for (uint32_t i = 0; i < ifmt_ctx->nb_streams; i++)
{
    AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流
    //如果是音频流,则打印音频的信息
    if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type)
    {
    	printf("----- Audio info:\n");
    	//下面可以通过in_stream获取音频的各种属性
    }
    else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  //如果是视频流,则打印视频的信息
    {
        printf("----- Video info:\n");
        //下面可以通过in_stream获取视频的各种属性
    }    
}

4、解封装的代码

解封装MP4文件,将音频流和视频流从中读取出来,

#include <stdio.h>
#include <libavformat/avformat.h>


int main(int argc, char **argv)
{
    //打开网络流。这里如果只需要读取本地媒体文件,不需要用到网络功能,可以不用加上这一句
	 //avformat_network_init();
    const char *default_filename = "believe.mp4";
    char *in_filename = NULL;
    if(argv[1] == NULL)
    {
        in_filename = default_filename;
    }
    else
    {
        in_filename = argv[1];
    }
    printf("in_filename = %s\n", in_filename);

    //AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体
    AVFormatContext *ifmt_ctx = NULL;           // 输入文件的demux

    int videoindex = -1;        // 视频索引
    int audioindex = -1;        // 音频索引

    // 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接
    int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);
    if (ret < 0)  //如果打开媒体文件失败,打印失败原因
    {
        char buf[1024] = { 0 };
        av_strerror(ret, buf, sizeof(buf) - 1);
        printf("open %s failed:%s\n", in_filename, buf);
        goto failed;
    }

    ret = avformat_find_stream_info(ifmt_ctx, NULL);
    if (ret < 0)  //如果打开媒体文件失败,打印失败原因
    {
        char buf[1024] = { 0 };
        av_strerror(ret, buf, sizeof(buf) - 1);
        printf("avformat_find_stream_info %s failed:%s\n", in_filename, buf);
        goto failed;
    }

    //打开媒体文件成功
    printf_s("\n==== av_dump_format in_filename:%s ===\n", in_filename);
    av_dump_format(ifmt_ctx, 0, in_filename, 0);
    printf_s("\n==== av_dump_format finish =======\n\n");
    // url: 调用avformat_open_input读取到的媒体文件的路径/名字
    printf("media name:%s\n", ifmt_ctx->url);
    // nb_streams: nb_streams媒体流数量
    printf("stream number:%d\n", ifmt_ctx->nb_streams);
    // bit_rate: 媒体文件的码率,单位为bps
    printf("media average ratio:%lldkbps\n",(int64_t)(ifmt_ctx->bit_rate/1024));
    // 时间
    int total_seconds, hour, minute, second;
    // duration: 媒体文件时长,单位微妙
    total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;  // 1000us = 1ms, 1000ms = 1秒
    hour = total_seconds / 3600;
    minute = (total_seconds % 3600) / 60;
    second = (total_seconds % 60);
    //通过上述运算,可以得到媒体文件的总时长
    printf("total duration: %02d:%02d:%02d\n", hour, minute, second);
    printf("\n");
    /*
     * 老版本通过遍历的方式读取媒体文件视频和音频的信息
     * 新版本的FFmpeg新增加了函数av_find_best_stream,也可以取得同样的效果
     */
    for (uint32_t i = 0; i < ifmt_ctx->nb_streams; i++)
    {
        AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流
        //如果是音频流,则打印音频的信息
        if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type)
        {
            printf("----- Audio info:\n");
            // index: 每个流成分在ffmpeg解复用分析后都有唯一的index作为标识
            printf("index:%d\n", in_stream->index);
            // sample_rate: 音频编解码器的采样率,单位为Hz
            printf("samplerate:%dHz\n", in_stream->codecpar->sample_rate);
            // codecpar->format: 音频采样格式
            if (AV_SAMPLE_FMT_FLTP == in_stream->codecpar->format)
            {
                printf("sampleformat:AV_SAMPLE_FMT_FLTP\n");
            }
            else if (AV_SAMPLE_FMT_S16P == in_stream->codecpar->format)
            {
                printf("sampleformat:AV_SAMPLE_FMT_S16P\n");
            }
            // channels: 音频信道数目
            printf("channel number:%d\n", in_stream->codecpar->channels);
            // codec_id: 音频压缩编码格式
            if (AV_CODEC_ID_AAC == in_stream->codecpar->codec_id)
            {
                printf("audio codec:AAC\n");
            }
            else if (AV_CODEC_ID_MP3 == in_stream->codecpar->codec_id)
            {
                printf("audio codec:MP3\n");
            }
            else
            {
                printf("audio codec_id:%d\n", in_stream->codecpar->codec_id);
            }
            // 音频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
            if(in_stream->duration != AV_NOPTS_VALUE)
            {
                int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);//采样点的个数乘上每个采样点的时长
                //将音频总时长转换为时分秒的格式打印到控制台上
                printf("audio duration: %02d:%02d:%02d\n",
                       duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));
            }
            else
            {
                printf("audio duration unknown");
            }

            printf("\n");

            audioindex = i; // 获取音频的索引
        }
        else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  //如果是视频流,则打印视频的信息
        {
            printf("----- Video info:\n");
            printf("index:%d\n", in_stream->index);
            // avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧
            printf("fps:%lffps\n", av_q2d(in_stream->avg_frame_rate));
            if (AV_CODEC_ID_MPEG4 == in_stream->codecpar->codec_id) //视频压缩编码格式
            {
                printf("video codec:MPEG4\n");
            }
            else if (AV_CODEC_ID_H264 == in_stream->codecpar->codec_id) //视频压缩编码格式
            {
                printf("video codec:H264\n");
            }
            else
            {
                printf("video codec_id:%d\n", in_stream->codecpar->codec_id);
            }
            // 视频帧宽度和帧高度
            printf("width:%d height:%d\n", in_stream->codecpar->width,
                   in_stream->codecpar->height);
            //视频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
            if(in_stream->duration != AV_NOPTS_VALUE)
            {
                int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);
                printf("video duration: %02d:%02d:%02d\n",
                       duration_video / 3600,
                       (duration_video % 3600) / 60,
                       (duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上
            }
            else
            {
                printf("video duration unknown");
            }

            printf("\n");
            videoindex = i;
        }
    }

/****************分离音视频流*********************/
    AVPacket *pkt = av_packet_alloc();

    int pkt_count = 0;
    int print_max_count = 10;
    printf("\n-----av_read_frame start\n");
    while (1)
    {
        ret = av_read_frame(ifmt_ctx, pkt);//每次都会分配一个包,拷贝到buffer中,所以每次都要在最后调用av_packet_unref(pkt)释放
        if (ret < 0)
        {
            printf("av_read_frame end\n");
            break;
        }

        if(pkt_count++ < print_max_count)
        {
            if (pkt->stream_index == audioindex)
            {
                printf("audio pts: %lld\n", pkt->pts);
                printf("audio dts: %lld\n", pkt->dts);
                printf("audio size: %d\n", pkt->size);
                printf("audio pos: %lld\n", pkt->pos);
                printf("audio duration: %lf\n\n",
                       pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));
                //这里就是1024*(1/48000)=0.02133333ms(数值只是这个包的,不代表所有,根据采样率不同48K或者其他)
            }
            else if (pkt->stream_index == videoindex)
            {
                printf("video pts: %lld\n", pkt->pts);
                printf("video dts: %lld\n", pkt->dts);
                printf("video size: %d\n", pkt->size);
                printf("video pos: %lld\n", pkt->pos);
                printf("video duration: %lf\n\n",
                       pkt->duration * av_q2d(ifmt_ctx->streams[videoindex]->time_base));
            }
            else
            {
                printf("unknown stream_index:\n", pkt->stream_index);
            }
        }
        av_packet_unref(pkt);
    }

    if(pkt)
        av_packet_free(&pkt);
failed:
    if(ifmt_ctx)
        avformat_close_input(&ifmt_ctx);


    getchar(); //加上这一句,防止程序打印完信息马上退出
    return 0;
}

5、重点

1、avformat_open_input和avformat_find_stream_info分别用于打开一个流和分析流信息。
2、在初始信息不足的情况下(比如FLV和H264文件),avformat_find_stream_info接口需要在内部调用read_frame_internal接口读取流数据(音视频帧),然后再分析后,设置核心数据结构AVFormatContext。
3、由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ffmpeg基础一:解封装 的相关文章

随机推荐

  • 做出刷屏文字的6大哲学

    title 做出刷屏文字的6大哲学 date 2019 09 05 23 39 16 tags 刷屏 文字 哲学 published true hideInList false feature https s2 ax1x com 2019
  • 遥感影像语义分割论文笔记(二)Category Boundary Detection Network

    A semantic segmentation method with category boundary for Land Use and Land Cover LULC mapping of Very High Resolution V
  • 配置 gitlab https 访问

    文章目录 1 备份 2 生成SSL证书 3 配置文件 4 重启 5 访问 1 备份 docker exec ti gitlab ce gitlab rake gitlab backup create 2 生成SSL证书 yum instal
  • 药物 3D 打印新突破:圣地亚哥大学用机器学习筛选喷墨打印生物墨水,准确率高达 97.22%

    内容一览 药物喷墨打印是一种高度灵活和智能化的制药方式 据相关报告统计 该领域市场规模将在不久的未来呈现指数级增长 过往 筛选合适生物墨水的方法费时且费力 因此也成为药物喷墨打印领域面临的主要挑战之一 为解决这一问题 国际药剂学期刊 Int
  • 高斯牛顿法求非线性最小二乘的步骤和c++代码实现

    slam图优化的本质是一个非线性优化问题 Gauss Newton求解步骤 1 线性化误差函数 2 构建线性系统 3 求解线性系统 4 更新解 并不断迭代直至收敛 一个简单的代码实现 一维参数xy 高维变为对应的矩阵即可 include
  • 如何用cublas计算逆矩阵?

    cublas的文档中提供了一个用LU分解求逆矩阵的方法 需要用到两个函数 cublas
  • yum Error downloading packages: xxx [Errno 256] No more mirrors to try.

    问题 解决 yum clean all执行后再执行yum makecache 清除缓存以及重新下载相关包信息缓存到本地
  • C语言之自定义函数的实现

    自定义函数的作用 方便管理代码 编写程序时思路清晰 代码复用 同一段代码可以在不同位置多次执行 函数声明 使用 返回值类型 函数名 类型 变量名 类型 变量名 例如 int sum int num1 int num2 实际上 在函数声明时
  • 【Leetcode】154. 寻找旋转排序数组中的最小值 II

    题目描述 已知一个长度为 n 的数组 预先按照升序排列 经由 1 到 n 次 旋转 后 得到输入数组 例如 原数组 nums 0 1 4 4 5 6 7 在变化后可能得到 若旋转 4 次 则可以得到 4 5 6 7 0 1 4 若旋转 7
  • php脚本中利用mail函数发送邮件及问题归纳

    最近在学php脚本语言 书中有一个实例 服务器端将浏览器发送的信息通过php脚本 利用mail函数发送到一个邮箱 自己讲书中实例代码上传至服务器 在浏览器上填写表单 并提交后 邮箱迟迟收不到邮件 1 php中mail函数发送邮件失败问题 邮
  • 2021-10-19,Oracle VM VirtualBox中安装增强功能失败

    问题 Oracle VM VirtualBox中安装增强功能失败 根据给出的信息提示进行解决 This system is currently not set up to build kernel modules Please instal
  • 静态博客提升访问速度:内嵌CSS,异步加载js,压缩HTML

    在谷歌搜索的功能速度 实验性 中推荐使用工具PageSpeed Insights查看我的网页访问速度情况 PageSpeed Insights 分数很低 只有33分 优化建议的第一条是移除阻塞渲染的资源 里面列出了非异步加载的css和js文
  • 公告

    作为一个稳定 高效 安全的区块链底层平台 FISCO BCOS一方面致力于保障系统稳定性 兼容性 鲁棒性 易用性 另一方面专注于提升区块链底层平台的性能 可扩展性 自FISCO BCOS v3 0开始 社区更是秉着开源 开放 联合共建的原则
  • 最长公共子序列算法_解决最长公共子序列问题的算法和过程

    最长公共子序列算法 最长的公共子序列 Longest common Subsequence Let X and Y be two subsequences and by LCS algorithm we can find a maximum
  • 机器学习笔记——概率生成模型

    假设有两类数据 每一类都有若干个样本 概率生成模型认为每一类数据都服从某一种分布 如高斯分布 从两类训练数据中得到两个高斯分布的密度函数 具体的是获得均值和方差两个参数 测试样本输入到其中一个高斯分布函数 得到的概率值若大于0 5 则说明该
  • nestjs:判断字符串是否是邮箱格式

    问题 如标题 参考 js判断邮箱格式是否正确 掘金 解决 function checkIsMail mail string const regex a zA Z0 9 a zA Z0 9 a zA Z0 9 2 4 return regex
  • c++之全局变量

    全局变量需要我们在头文件声明 在cpp文件定义 请记住尽量这么做 保持操作的规范性 全局变量的使用 一般为两种 一种是不包含头文件 一种是包含了头文件 1 不包含头文件 例如我在test02 h中声明了全局变量 接下来在cpp中定义 如果想
  • 九十六、kettle版本8.2连接数据库的中文乱码问题

    在学习使用kettle 从Excel导数据到MySQL数据库的过程中踩了不少坑 总结一下以做前车之鉴 希望能给大家提供一点帮助 关于中文乱码目前找到了两种也可以叫三种方法 因为版本不同而方法可能不同 目前我使用第一种防范解决了中文乱码问题
  • React:状态提升案例-烧开水

    1 定义Boilingverdict组件 function Boilingverdict props if props celsius gt 100 return h1 水烧开了 h1 else return h1 水凉凉 h1 expor
  • ffmpeg基础一:解封装

    参考 零声学院 1 解封装流程 2 打开文件或媒体 获取文件或媒体的媒体信息AVFormatContext AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体 AVFormatContext ifmt ctx