一、前言:
大家好,我是txp,好久没有写技术原创文章了,久等了,记得之前写的播放器学习文章后面也没有继续更了;主要原因这段时间一个是由于工作的原因,二来主要是最近利用空闲时间看完了今年的第四本非技术书籍,目前又买了几本新的非技术书籍学习!在书上看到学到的知识,目前我都在总结输出!一些学习经验和工作当中的经验,我会慢慢录制视频出来,视频比文字更加生动(ps:因为之前录制的视频,不知道如何添加字幕,所以现在知道了,做就要认真做,就要把一件事情做好!)
还有就是前段时间参与写的一本入门技术书籍也出版上市了,人生的第一次,非常感谢!!!继续加油,冲起来,
好了,接下来开始给大家分享FFmpeg解封装演示,在写文章之前呢,还是说明一下,这篇文章我不会去把每个api接口都说明一下;我想准备一个专辑专门去讲,我会结合官网手册和FFmpeg源码接口说明去写,这目前是我的一个初步想法,具体行动的话,如果我开始写了第一篇关于api说明,那就说明计划已经执行起来了。。。。。
二、解封装流程:
这里简单说一下,所谓的解封装就是把一个音视频文件拆开来,就像你把一个完整的玩具拆开来一样,然后可以对模块进行分析,当然与解封装对应就有封装了,这个不是本文的重点。
下面是解封装的流程:
- 1、注册相关模块(av_register_all; avformat_network_init)
- 2、根据即将输出的文件名、获取封装信息上下文AVFormatContext(avformat_alloc_output_context2)
- 3、打开输出文件IO(avio_open)
- 4、添加音视频流(avformat_new_stream)
- 5、封装文件头信息(avformat_write_header)
- 6、向文件中写入数据包,如果包含视频、音频等多个码流的数据包,则按照时间戳大小交织写入(av_interleaved_write_frame)
- 7、封装文件尾信息(av_write_trailer)
- 8、关闭操作
下面我直接把整个代码给出(这个代码测试mp4格式):
代码语言:javascript复制#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 = %sn", 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:%sn", 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:%sn", 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 =======nn");
// url: 调用avformat_open_input读取到的媒体文件的路径/名字
printf("media name:%sn", ifmt_ctx->url);
// nb_streams: nb_streams媒体流数量
printf("stream number:%dn", ifmt_ctx->nb_streams);
// bit_rate: 媒体文件的码率,单位为bps
printf("media average ratio:%lldkbpsn",(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: d:d:dn", 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:%dn", in_stream->index);
// sample_rate: 音频编解码器的采样率,单位为Hz
printf("samplerate:%dHzn", in_stream->codecpar->sample_rate);
// codecpar->format: 音频采样格式
if (AV_SAMPLE_FMT_FLTP == in_stream->codecpar->format)
{
printf("sampleformat:AV_SAMPLE_FMT_FLTPn");
}
else if (AV_SAMPLE_FMT_S16P == in_stream->codecpar->format)
{
printf("sampleformat:AV_SAMPLE_FMT_S16Pn");
}
// channels: 音频信道数目
printf("channel number:%dn", in_stream->codecpar->channels);
// codec_id: 音频压缩编码格式
if (AV_CODEC_ID_AAC == in_stream->codecpar->codec_id)
{
printf("audio codec:AACn");
}
else if (AV_CODEC_ID_MP3 == in_stream->codecpar->codec_id)
{
printf("audio codec:MP3n");
}
else
{
printf("audio codec_id:%dn", 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: d:d:dn",
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:%dn", in_stream->index);
// avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧
printf("fps:%lffpsn", av_q2d(in_stream->avg_frame_rate));
if (AV_CODEC_ID_MPEG4 == in_stream->codecpar->codec_id) //视频压缩编码格式
{
printf("video codec:MPEG4n");
}
else if (AV_CODEC_ID_H264 == in_stream->codecpar->codec_id) //视频压缩编码格式
{
printf("video codec:H264n");
}
else
{
printf("video codec_id:%dn", in_stream->codecpar->codec_id);
}
// 视频帧宽度和帧高度
printf("width:%d height:%dn", 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: d:d:dn",
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 startn");
while (1)
{
ret = av_read_frame(ifmt_ctx, pkt);
if (ret < 0)
{
printf("av_read_frame endn");
break;
}
if(pkt_count < print_max_count)
{
if (pkt->stream_index == audioindex)
{
printf("audio pts: %lldn", pkt->pts);
printf("audio dts: %lldn", pkt->dts);
printf("audio size: %dn", pkt->size);
printf("audio pos: %lldn", pkt->pos);
printf("audio duration: %lfnn",
pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));
}
else if (pkt->stream_index == videoindex)
{
printf("video pts: %lldn", pkt->pts);
printf("video dts: %lldn", pkt->dts);
printf("video size: %dn", pkt->size);
printf("video pos: %lldn", pkt->pos);
printf("video duration: %lfnn",
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;
}
首先演示一下mp4格式输出结果:
输出结果把音频和视频流分开来解析了,具体可以看上面的源码就知道了。
上面的代码注解已经非常详细了,理解起来应该不难,下面我debug一下可能有人看不懂的地方:
代码语言:javascript复制 //视频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
if(in_stream->duration != AV_NOPTS_VALUE)
{
int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);
printf("video duration: d:d:dn",
duration_video / 3600,
(duration_video % 3600) / 60,
(duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上
}
这段代码视频时间计算可能大家不好理解,特别是
代码语言:javascript复制av_q2d(in_stream->time_base);
它是一个内联函数:
代码语言:javascript复制/**
* Convert an AVRational to a `double`.
* @param a AVRational to convert
* @return `a` in floating-point form
* @see av_d2q()
*/
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}
那这里的num和den是多少呢?下面我们来看一下debug:
通过debug调试,我们可以发现den的值为15360,num的值为1,那么:
代码语言:javascript复制a.num / (double) a.den = 1 / 15360
这样就可以算出整个视频流的总时间了:
代码语言:javascript复制 int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);
三、总结:
好了,本期文章就分享到这里,我是txp,我们下期见!