1. MP4格式分析
参考MP4格式分析
2. 相关API及数据结构
//打开要读取的文件,获得上下文
int avformat_open_input(
AVFormatContext **ps, //上下文
const char *url, //文件路径
ff_const59 AVInputFormat *fmt, //输入的格式
AVDictionary **options //选项
);
//获取ic的信息
int avformat_find_stream_info(
AVFormatContext *ic, //上下文
AVDictionary **options //选项
);
//从ic读取数据到packet
int av_read_frame(
AVFormatContext *ic,
AVPacket *pkt
);
1. pkt本身的空间函数不维护,但每调用一次该函数,就会申请一次buffer空间,因此在每次调用完成后需要清理pkt的buffer空间的引用计数
2. pkt->pts/dts在无B帧时是相同的,若有B帧,因为后解码但却可能先播放,会导致二者不同
3. 但当读取的是H264数据时,其内部并未封装头信息,因此无法帧索引无法随意拖动进度条,因此pts、dts也不存在。但与AVstream->time_base时间基相关可以自己设置。
//pts转换:根据原来的时间基和新的时间基,将原来的pts转为新的pts
int64_t av_rescale_q_rnd(
int64_t a, //pts
AVRational bq, //原来的时间基
AVRational cq, //现在的时间基
enum AVRounding rnd
);
实际上就是 a * bq / cq, 返回运算的结果
rnd:
AV_ROUND_ZERO/INF/DOWN/UP/NEAR_INF四舍五入
struct AVFormatContext
{
unsigned int nb_streams; //streams的数量
AVStream **streams; //数据流的配置信息
void *priv_data; //AVOptions
AVIOContext *pb; //I/O
int64_t duration;
int64_t bit_rate;
char *url;
}
struct AVStream
{
int index; //流的索引号
AVRational time_base; //时间基
AVRational avg_frame_rate;
AVCodecParameters *codecpar;//配置信息
}
struct AVCodecParameters
{
AVMediaType codec_type;
AVCodecID codec_id;
int format;
int64_t bit_rate;
int width;
int height;
uint64_t channel_layout;
int channels;
int sample_rate;
int frame_size;
}
option_table.h中含有AVOption avformat_options结构体的定义
3. 对视频进行解封装
- 打开文件
- 获取视频流信息
- 输出视频信息
- 读取每一帧的数据
- 对每一帧数据进行解码
- 对解码的每一帧数据进行渲染
#include <iostream>
#include "xvideo_view.h"
#include "xdecode.h"
#include "xencode.h"
extern "C"
{
#include <libavformat/avformat.h>
}
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
//#pragma comment(lib, "avformat.lib")
using namespace std;
int main()
{
//1.打开文件
AVFormatContext* fmt_ctx = NULL;
const char* url = "v1080.mp4";
avformat_open_input(&fmt_ctx, url, NULL, NULL);
//2.获取数据流到上下文
avformat_find_stream_info(fmt_ctx, NULL);
//3.输出文件的数据流信息
av_dump_format(fmt_ctx, 0, url, 0);
//获取视频流数据
AVStream* vs = NULL;
for (int i = 0; i < fmt_ctx->nb_streams; i++)
{
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
vs = fmt_ctx->streams[i];
}
break;
}
//4.读取每帧视频数据
//打开编码器
auto decoder = new XDecode();
auto decoder_ctx = decoder->Create(vs->codecpar->codec_id, NULL);
avcodec_parameters_to_context(decoder_ctx, vs->codecpar); //将编码器参数传给文件上下文
decoder->set_c(decoder_ctx);
decoder->Open();
auto frame = decoder->CreateFrame();
AVPacket packet;
//打开渲染器
auto view = XVideoView::Create();
view->Init(vs->codecpar->width, vs->codecpar->height, (XVideoView::Format)vs->codecpar->format);
while (true)
{
av_read_frame(fmt_ctx, &packet);
//5.对每帧数据解码
decoder->Send(&packet);
decoder->Recv(frame);
//6.渲染解码后的数据
view->DrawFrame(frame);
av_packet_unref(&packet);
}
//释放资源
av_frame_free(&frame);
delete decoder;
avformat_close_input(&fmt_ctx);
}
4. 封装
- 创建封装文件的格式上下文
- 创建封装的流
- 打开文件
- 设置封装流的封装编码等参数
申请AVFormatContext上下文
int avformat_alloc_output_context2(
AVFormatContext **ctx,
ff_const59 AVOutputFormat *oformat,
const char *format_name,
const char *filename
);
创建要封装的数据流
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
打开或者关闭文件
int avio_open(AVIOContext **s, const char *url, int flags);
int avio_closep(AVIOContext **s);
编码器参数拷贝
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
写入数据到文件
int avformat_write_header(AVFormatContext *s, AVDictionary **options); 写头部信息
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt); 写具体数据信息
int av_write_trailer(AVFormatContext *s); 写尾部信息
#include <iostream>
#include "xvideo_view.h"
#include "xdecode.h"
#include "xencode.h"
extern "C"
{
#include <libavformat/avformat.h>
}
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
//#pragma comment(lib, "avformat.lib")
using namespace std;
int main()
{
//1.打开文件
AVFormatContext* fmt_ctx = NULL;
const char* url = "v1080.mp4";
avformat_open_input(&fmt_ctx, url, NULL, NULL);
//2.获取数据流到上下文
avformat_find_stream_info(fmt_ctx, NULL);
//3.输出文件的数据流信息
av_dump_format(fmt_ctx, 0, url, 0);
//获取视频流数据
AVStream* vs = NULL;
AVStream* as = NULL;
for (int i = 0; i < fmt_ctx->nb_streams; i++)
{
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
vs = fmt_ctx->streams[i];
}
else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
as = fmt_ctx->streams[i];
}
}
//创建输出文件上下文
const char* out_url = "out.mp4";
AVFormatContext* out_fmt_ctx = NULL;
avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, out_url);
//创建输出的数据流
auto v_s = avformat_new_stream(out_fmt_ctx, NULL);
auto a_s = avformat_new_stream(out_fmt_ctx, NULL);
//打开输出文件
avio_open(&out_fmt_ctx->pb, out_url, AVIO_FLAG_WRITE);
//设置视频流参数
if (v_s)
{
v_s->time_base = vs->time_base;
avcodec_parameters_copy(v_s->codecpar, vs->codecpar);
}
if (a_s)
{
a_s->time_base = as->time_base;
avcodec_parameters_copy(a_s->codecpar, as->codecpar);
}
//写入头部信息
avformat_write_header(out_fmt_ctx, NULL);
//av_dump_format(out_fmt_ctx, 0, out_url, 1);
//4.读取每帧视频数据
AVPacket packet;
while (true)
{
//cout << "test" << endl;
auto ret = av_read_frame(fmt_ctx, &packet);
if (ret != 0)
{
break;
}
//写入数据
av_interleaved_write_frame(out_fmt_ctx, &packet);
//av_packet_unref(&packet);
}
//写入尾部信息
av_write_trailer(out_fmt_ctx);
//释放资源
avformat_close_input(&fmt_ctx);
avio_closep(&out_fmt_ctx->pb);
avformat_close_input(&out_fmt_ctx);
}
截取10-20之间的10s视频
#include <iostream>
#include "xvideo_view.h"
#include "xdecode.h"
#include "xencode.h"
extern "C"
{
#include <libavformat/avformat.h>
}
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
//#pragma comment(lib, "avformat.lib")
using namespace std;
/*解封装视频、截取10s后封装成新的视频*/
int main()
{
int ret = -1;
/*解封装视频*/
//打开输入文件并打印信息
AVFormatContext* in_fmt_ctx = NULL;
const char* in_url = "v1080.mp4";
avformat_open_input(&in_fmt_ctx, in_url, NULL, NULL);
avformat_find_stream_info(in_fmt_ctx, NULL);
av_dump_format(in_fmt_ctx, 0, in_url, 0);
//打开输出文件,创建流
AVFormatContext* out_fmt_ctx = NULL;
const char* out_url = "out.mp4";
avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, out_url);
AVStream *out_vs = avformat_new_stream(out_fmt_ctx, NULL);
AVStream* out_as = avformat_new_stream(out_fmt_ctx, NULL);
avio_open(&out_fmt_ctx->pb, out_url, AVIO_FLAG_WRITE);
//设置输出文件的流参数
AVStream* in_vs = NULL;
AVStream* in_as = NULL;
for (int i = 0; i < in_fmt_ctx->nb_streams; i++)
{
if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
in_vs = in_fmt_ctx->streams[i];
out_vs->time_base = in_vs->time_base;
avcodec_parameters_copy(out_vs->codecpar, in_vs->codecpar);
}
else if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
in_as = in_fmt_ctx->streams[i];
out_as->time_base = in_as->time_base;
avcodec_parameters_copy(out_as->codecpar, in_as->codecpar);
}
}
avformat_write_header(out_fmt_ctx, NULL); //写入头信息
av_dump_format(out_fmt_ctx, 0, out_url, 1);
//设置截取的时间
double s_t = 10.0, e_t = 20.0;
auto in_vs_tmp = (double)in_vs->time_base.den / (double)in_vs->time_base.num; //时间间隔
auto in_as_tmp = (double)in_as->time_base.den / (double)in_as->time_base.num;
auto s_pts = s_t * in_vs_tmp; //截取的pts
auto a_s_pts = s_t * in_as_tmp;
auto e_pts = e_t * in_vs_tmp;
/*截取视频后封装视频*/
av_seek_frame(in_fmt_ctx, in_vs->index, s_pts, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME); //跳到截取位置
//av_seek_frame(in_fmt_ctx, in_as->index, s_pts, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
AVPacket packet;
while (true)
{
//读取数据并截取
ret = av_read_frame(in_fmt_ctx, &packet);
if (ret != 0)
{
break;
}
//修改新视频的pts、dts、duration后写入文件
if (packet.stream_index == in_vs->index)
{
if (packet.pts >= e_pts) //截取结束
{
av_packet_unref(&packet);
break;
}
cout << "vs_pkt_pts" << packet.pts << endl;
packet.pts = av_rescale_q_rnd(packet.pts - s_pts, in_vs->time_base, out_vs->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.dts = av_rescale_q_rnd(packet.dts - s_pts, in_vs->time_base, out_vs->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.duration = av_rescale_q(packet.duration, in_vs->time_base, out_vs->time_base);
}
else if (packet.stream_index == in_as->index)
{
cout << "as_pkt_pts" << packet.pts << endl;
packet.pts = av_rescale_q_rnd(packet.pts - a_s_pts, in_as->time_base, out_as->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.dts = av_rescale_q_rnd(packet.dts - a_s_pts, in_as->time_base, out_as->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.duration = av_rescale_q(packet.duration, in_as->time_base, out_as->time_base);
}
packet.pos = -1;
av_interleaved_write_frame(out_fmt_ctx, &packet);
}
av_write_trailer(out_fmt_ctx);
avio_closep(&out_fmt_ctx->pb);
avformat_close_input(&in_fmt_ctx);
avformat_free_context(out_fmt_ctx);
}