文章目录
- ffmpeg多媒体文件格式转换流程
- Demuxing-Module解封装模块
- Muxing-Module封装模块
- ffmpeg多媒体文件格式转换器(无编解码)官方示例
- GitHub:[https://github.com/AnJiaoDe/FFmpegDemo](https://github.com/AnJiaoDe/FFmpegDemo)
- ffmpeg多媒体文件格式转换器(无编解码)详解
- avformat_open_input()
- avformat_find_stream_info()
- av_dump_format()
- avformat_alloc_output_context2()
- AVFormatContext中的unsigned int nb_streams
- av_mallocz_array()
- AVOutputFormat
- typedef struct AVCodecParameters
- enum AVMediaType
- avformat_new_stream()
- avcodec_parameters_copy
- codecpar->codec_tag = 0疑问?
- avio_open()
- AVIOContext
- AVFormatContext中的AVIOContext *pb
- avformat_write_header()
- av_read_frame()
- ffmpeg时间基相关详解
- av_rescale_q_rnd()
- enum AVRounding
- AVStream中的AVRational time_base
- AVPacket中的int64_t pts
- AVPacket中的int64_t dts
- AVPacket中的int64_t duration
- av_rescale_q()
- av_interleaved_write_frame()
- Interleaving(交叉存取技术)
- av_write_frame()
- 欢迎联系、指正、批评
ffmpeg格式转换,主要看libavformat库中的Demuxing-Module和Muxing-Module
ffmpeg多媒体文件格式转换流程
文件格式转换,省略了对指定流的解码和编码步骤,它只进行解封装和封装:
_______ ______________ ________
| | | | | |
| input | demuxer | encoded data | muxer | output |
| file | ---------> | packets | -------> | file |
|_______| |______________| |________|
由于没有解码或编码,它非常快,没有质量损失。
Demuxing-Module解封装模块
格式解析器读取媒体文件并将其拆分为数据块(数据包)。
一个数据包,包含一个或多个属于单个基本流的编码帧。在libavformat的API中,解封装过程由如下几步完成:avformat_open_input()函数用于打开文件,av_read_Frame()用于读取单个数据包,最后由avformat_close_input()来进行清理。
Opening a media file打开一个媒体文件
打开文件所需的最小信息是它的url,它被传递给avformat_open_input(),如下代码所示:
int avformat_open_input(AVFormatContext **ps, const char *url,
ff_const59 AVInputFormat *fmt, AVDictionary **options);
const char *url = "file:in.mp3";
AVFormatContext *s = NULL;
int ret = avformat_open_input(&s, url, NULL, NULL);
if (ret < 0)
abort();
上面的代码会为AVFormatContext对象分配内存,打开指定的文件(自动检测格式)并读取文件头,将存储在其中的信息导出到AVFormatContext *s。有些格式没有文件头,或者没有在其中存储足够的信息,因此建议您调用avformat_find_stream_info()函数,该函数尝试读取和解码几个帧以查找丢失的信息。
在某些情况下,您可能希望使用avformat_alloc_context()预先初始化AVFormatContext对象,并在将其传递给avformat_open_input()
之前对其进行一些调整。在您希望不用libavformat的内部I/O模块,而使用自定义函数来读取输入数据的时候会用到。为此,使用avio_alloc_text()创建自己的AVIOContext 对象,并将读取回调传递给它。然后将avformattext的pb属性设置为新创建的aviotext。
由于打开的文件的格式通常在avformat_open_input()返回之后才知道,因此不可能在预先分配的上下文上设置demuxer私有属性。相反,应该将这些属性包装到AVDictionary对象,再传递给avformat_open_input():
int avformat_open_input(AVFormatContext **ps, const char *url,
ff_const59 AVInputFormat *fmt, AVDictionary **options);
AVDictionary *options = NULL;
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "pixel_format", "rgb24", 0);
if (avformat_open_input(&s, url, NULL, &options) < 0)
abort();
av_dict_free(&options);
此代码将私有属性“视频_大小”和“像素_格式”传递给解析器。这些属性对于原始视频解析器是必要的,否则它不知道如何解析原始视频数据。如果格式与原始视频不同,这些属性将不会被解析器识别,因此不会被应用。然后在选项字典中返回这些未识别的选项(已识别的选项被使用)。如下程序可以根据自己的意愿处理无法识别的属性:
AVDictionaryEntry *e;
if (e = av_dict_get(options, "", NULL, AV_DICT_IGNORE_SUFFIX)) {
fprintf(stderr, "Option %s not recognized by the demuxer.\n", e->key);
abort();
}
读取完该文件后,必须使用avformat_close_input()关闭该文件。它将释放与文件相关的所有内存。
Reading from an opened file从一个打开的文件读取信息
从打开文件的AVFormatContext 读取数据是通过反复调用av_read_frame()来完成的。如果每个调用成功,则将返回一个AVPacket(AV数据包) ,它有一个stream_index属性,该属性记录了一个含有编码数据的AVStream的索引值。如果调用方希望解码数据,则可以将此数据包直接传递到libavcodec解码函数avcodec_send_packet()
或avcodec_decode_subtitle 2()。
如果已知输入流的avpacket.pts、avpacket.dts和avpacket.duration 时间信息,应该将其设置到输出流。如果流里不包含该信息,它们将不必设置到输出流(例如,AV_NOPTS_VALUE for pts/dts, 0 for duration)。时间信息将以AVStream.time_base
时钟基为单位表示,时间信息必须乘以时钟基才能将它们转换为秒。
如果在返回的数据包上设置avpacket.buf,则动态分配数据包,用户可以无限期地保留它。否则,如果avpacket.buf为NULL,则数据包数据由demuxer内部的静态存储所支持,并且数据包仅在下一个av_read_frame()
调用或关闭文件之前才有效。如果调用方需要更长的生存期,av_packet_make_refcounted()将确保数据被计数,并在必要时复制数据。在这两种情况下,当包不再需要时,必须用av_packet_unref()释放数据包。
Muxing-Module封装模块
封装器以av数据包的形式封装编码的数据,并以指定的容器格式将其写入文件或其他输出字节流。
封装器的主要API函数有:avformat_write_header()用于写文件头,av_write_frame()
/ av_interleaved_write_frame()用于写数据包,还有av_write_trailer用于写文件尾完成文件写入。
在封装过程开始时,必须首先调用avformat_alloc_context()
来创建muing上下文。然后调用方通过填充上下文中的各个字段来设置muxer:
·必须将oformat 设置属性,以便决定使用什么封装器。
·除非格式为AVFMT_NOFILE类型,否则必须将pb字段设置为打开的io上下文,可以从avio_open2()返回,或者自定义。
·除非格式为AVFMT_NOSTREAMS 类型,否则至少必须使用avmat_new_stream()
函数创建一个流。调用方应填写流编解码器参数信息,如编解码器类型、id和其他参数(例如宽度/高度、像素或采样格式等)。流时基应该设置为调用方希望对此流使用的时间基(请注意,muxer实际使用的时间基可能有所不同,稍后将对此进行描述)。
·建议只手动初始化avcodecameters中的相关字段,而不是在重封装期间使用avcodec_properties_Copy():不能保证codec上下文值对输入和输出格式上下文都有效。
·调用方可以填写附加信息,如avformattext文档中描述的全局或流元数据、片段、程序等。这些信息是否实际存储在输出中取决于容器格式和muxer的支持。
当完成设置muing上下文时,调用方必须调用avformat_write_header()
来初始化muxer内部相关对象,并写文件头。在这一步中,实际是否将任何内容写入io上下文取决于muxer,但必须始终调用此函数。任何muxer私有属性都必须在Options参数中传递给此函数。
然后,通过反复调用av_write_frame()
或av_interleaved_write_frame()将数据传递到到muxer(请参阅这些函数的文档,文档详细描述了它们之间的差异;这2个函数其中只有一个可以与单个muing上下文一起使用,不应混合使用)。请注意,传递到muxer的数据包上的时间信息必须和avstream的时间基一致。该时间基由muxer设置(在avformat_write_header()步骤中),并且可能与调用方请求的时间基不同。
一旦所有数据都写入,调用者必须调用av_write_trailer()
来释放所有packet缓冲区,并最终完成输出文件,然后关闭io上下文(如果有的话),最后用avformat_free_context()释放muxing上下文。
ffmpeg多媒体文件格式转换器(无编解码)官方示例
remuxer.h:
/*
* Copyright (c) 2013 Stefano Sabatini
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file
* libavformat/libavcodec demuxing and muxing API example.
*
* Remux streams from one container format to another.
* @example remuxing.c
*/
extern "C" {
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
}
static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag) {
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
// printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
// tag,av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
// av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
// av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
// pkt->stream_index);
}
int main_remuxer(char *inPath, char *outPath) {
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
const char *pathIn, *pathOut;
int ret, i;
// int stream_index = 0;
// int *stream_mapping = NULL;
// int stream_mapping_size = 0;
// if (argc < 3) {
// printf("usage: %s input output\n"
// "API example program to remux a media file with libavformat and libavcodec.\n"
// "The output format is guessed according to the file extension.\n"
// "\n", argv[0]);
// return 1;
// }
pathIn = inPath;
pathOut = outPath;
//打开一个输入流,并且读取其文件头。编解码器没有打开。
if ((ret = avformat_open_input(&ifmt_ctx, pathIn, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '%s'", pathIn);
goto end;
}
/**读取媒体文件的所有packets,获取流信息。
有些格式(如MPEG)没有文件头,或者没有在其中存储足够的信息,
因此建议您调用avformat_find_stream_info()函数,该函数尝试读取和解码几个帧以查找丢失的信息。
*/
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
/**打印输入或输出格式的详细信息,
is_output:0表示input,1表示output
*/
av_dump_format(ifmt_ctx, 0, pathIn, 0);
/**为输出格式初始化AVFormatContext指针。
*/
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, pathOut);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// stream_mapping_size = ifmt_ctx->nb_streams;
/**创建int数组
* @param nmemb Number of elements 数组元素的个数
* @param size Size of the single element 每个元素的内存长度
*/
// stream_mapping = static_cast<int *>(av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping)));
// if (!stream_mapping) {
// ret = AVERROR(ENOMEM);
// goto end;
// }
/**输出文件的格式,只有在封装时使用,必须在调用avformat_write_header()前初始化
*/
ofmt = ofmt_ctx->oformat;
/*AVFormatContext 结构体中定义了AVStream **streams 数组;
* nb_streams即为数组元素的个数
*/
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams[i];
/**当前流的编解码参数,
avformat_new_stream()调用后会初始化,
avformat_free_context()调用后会被释放。
解封装:在创建流的时候或者avformat_find_stream_info()调用后,被初始化;
封装:avformat_write_header()调用前,手动初始化
*/
AVCodecParameters *in_codecpar = in_stream->codecpar;
/**如果输入多媒体文件的当前遍历到的流的 媒体类型不是音频、视频、字幕,那么stream_mapping[i]赋值为-1
*/
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
// stream_mapping[i] = -1;
continue;
}
//记录流索引
// stream_mapping[i] = stream_index++;
/**创建一个用于输出的AVStream指针对象
*/
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
/**输出的AVCodecParameters指针所占内存被释放,然后将输入的AVCodecParameters指针内存拷贝到输出的AVCodecParameters中
*/
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
/**
* Additional information about the codec (corresponds to the AVI FOURCC).
uint32_t codec_tag;
为编解码器添加额外信息,这里懵逼了,这行不写,输出视频文件会有毛病
*/
out_stream->codecpar->codec_tag = 0;
}
/**打印输入或输出格式的详细信息,
is_output:0表示input,1表示output
*/
av_dump_format(ofmt_ctx, 0, pathOut, 1);
/**解封装时,AVFormatContext中的AVIOContext *pb, 可以在调用avformat_open_input()之前初始化,
或者通过调用avformat_open_input()初始化
封装时,AVFormatContext中的AVIOContext *pb,可以在调用avformat_write_header()之前初始化,
完事后必须释放AVFormatContext中的AVIOContext *pb占用的内存
如果ofmt->flags值为AVFMT_NOFILE,就不要初始化AVFormatContext中的AVIOContext *pb,在这种情况下,
解封装器/封装器将会通过其它方式处理I/O,而且AVFormatContext中的AVIOContext *pb为NULL
*/
if (!(ofmt->flags & AVFMT_NOFILE)) {
/**为对应url的文件初始化一个AVIOContext 二级指针对象
*/
ret = avio_open(&ofmt_ctx->pb, pathOut, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'", pathOut);
goto end;
}
}
//初始化流的私有数据并将流头写入输出媒体文件
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
while (1) {
AVStream *in_stream, *out_stream;
/**返回流的下一帧。
此函数读取存储在文件中的内容到AVPacket *pkt,而不验证是否存在解码器的有效帧。
它将*存储在文件中的内容分割成帧,并为每个调用返回一个AVPacket *pkt。
它不会*省略有效帧之间的无效数据,以便给解码器最大的解码信息。
返回0表示读取一帧成功,返回负数,表示出错了或者已经读到文件末尾了。
*/
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
//初始化输入的AVStream,AVpacket 中的stream_index定义了流的索引
in_stream = ifmt_ctx->streams[pkt.stream_index];
// if (pkt.stream_index >= stream_mapping_size ||stream_mapping[pkt.stream_index] < 0) {
// //清除packet占用的内存
// av_packet_unref(&pkt);
// continue;
// }
// pkt.stream_index = stream_mapping[pkt.stream_index];
//初始化输出的AVStream
out_stream = ofmt_ctx->streams[pkt.stream_index];
log_packet(ifmt_ctx, &pkt, "in");
/*pkt.pts **乘** in_stream->time_base **除** out_stream->time_base
得到out_stream下pkt的pts,输出文件的一帧数据包的pts
即同步了输入输出的显示时间戳
pkt为从输入文件读取的一帧的数据包,
* */
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
/**pkt.dts **乘** in_stream->time_base **除** out_stream->time_base
得到out_stream下pkt的dts ,输出文件的一帧数据包的dts
即同步了输入输出的解压时间戳
pkt为从输入文件读取的一帧的数据包,
*/
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
/**pkt.duration**乘** in_stream->time_base **除** out_stream->time_base
得到out_stream下pkt的duration ,输出文件的一帧数据包的持续时间
即同步了输入输出的持续时间戳
pkt为从输入文件读取的一帧的数据包,
*/
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
// pkt.pos = -1;
log_packet(ofmt_ctx, &pkt, "out");
/**将一帧数据包写入输出媒体文件。
此函数将根据需要在内部缓冲数据包,以确保输出文件中的数据包按照dts的顺序正确交叉存储。
调用者进行自己的交叉存储时,应该调用av_write_frame(),而不是这个函数。
使用此函数而不是av_write_frame()可以使muxers提前了解未来的数据包,例如改善MP4对VFR内容在碎片模式下的行为。
*/
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
//清除packet占用的内存
av_packet_unref(&pkt);
}
// *将流的尾部写入输出媒体文件,并且释放其私有数据占用的内存
av_write_trailer(ofmt_ctx);
end:
//关闭打开的input AVFormatContext。释放其所有内容占用的内存,赋值为NULL。
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
// 关闭被AVIOContext使用的资源,释放AVIOContext占用的内存并且置为NULL
avio_closep(&ofmt_ctx->pb);
//释输出的放AVFormatContext所有占用的内存
avformat_free_context(ofmt_ctx);
//释放内存
// av_freep(&stream_mapping);
if (ret < 0 && ret != AVERROR_EOF) {
// fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
main.cpp:
#include <iostream>
#include <remuxer.h>
int main() {
main_remuxer("../resources/video.avi","../resources/video.mp4");
system("pause");
return 0;
}
为了方便测试和学习,文件建得有点奇葩,并且去除了一些冗余代码,运行结果如图:
GitHub:https://github.com/AnJiaoDe/FFmpegDemo
ffmpeg多媒体文件格式转换器(无编解码)详解
在编辑器中点击函数,转到头文件函数声明处,查看说明,官网API的说明在头文件里都有
avformat_open_input()
int avformat_open_input(AVFormatContext **ps, const char *url,
ff_const59 AVInputFormat *fmt, AVDictionary **options);
打开一个输入流,并且读取其文件头。编解码器没有打开。
该输入流完事后必须调用avformat_close_input()关闭。
ps:为API使用者提供的二级指针AVFormatContext(由avformat_alloc_context()分配)
该指针可初始化为NULL,调用avformat_open_input()后,会为该指针赋值。
注意:如果打开视频文件流失败,该指针会被置为NULL。
url:视频文件的URL路径
fmt:如果传值非NULL,改参数要求必须明确指定输入视频文件的格式。
否则,视频文件的格式会自动检测。
返回0:表示操作成功;返回负数:表示失败。
注意:如果你想使用自定义的IO,需要预先分配format context 并且设置其pb属性。
示例:
if ((ret = avformat_open_input(&ifmt_ctx, pathIn, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '%s'", pathIn);
goto end;
}
avformat_find_stream_info()
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
读取媒体文件的所有packets,获取流信息。
有些格式(如MPEG)没有文件头,或者没有在其中存储足够的信息,因此建议您调用avformat_find_stream_info()函数,该函数尝试读取和解码几个帧以查找丢失的信息。
示例:
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
av_dump_format()
void av_dump_format(AVFormatContext *ic,int index,const char *url,int is_output);
打印输入文件或输出文件的详细信息,is_output:0表示input,1表示output
示例:
av_dump_format(ifmt_ctx, 0, pathIn, 0);
如下输入文件的详细信息:
Input #0, flv, from '../resources/video.flv':
Metadata:
metadatacreator : iku
hasKeyframes : true
hasVideo : true
hasAudio : true
hasMetadata : true
canSeekToEnd : false
datasize : 932906
videosize : 787866
audiosize : 140052
lasttimestamp : 34
lastkeyframetimestamp: 30
lastkeyframelocation: 886498
encoder : Lavf55.19.104
Duration: 00:00:34.20, start: 0.042000, bitrate: 394 kb/s
Stream #0:0: Video: h264 (High), yuv420p(progressive), 512x288 [SAR 1:1 DAR 16:9], 15 fps, 15 tbr, 1k tbn, 30 tbc
Stream #0:1: Audio: mp3, 44100 Hz, stereo, fltp, 128 kb/s
示例:
av_dump_format(ofmt_ctx, 0, pathOut, 1);
如下输出文件的详细信息:
Output #0, mp4, to '../resources/video.mp4':
Stream #0:0: Video: h264 (High), yuv420p(progressive), 512x288 [SAR 1:1 DAR 16:9], q=2-31
Stream #0:1: Audio: mp3, 44100 Hz, stereo, fltp, 128 kb/s
avformat_alloc_output_context2()
int avformat_alloc_output_context2(AVFormatContext **ctx,
ff_const59 AVOutputFormat *oformat,const char *format_name, const char *filename);
为输出格式初始化AVFormatContext指针。
示例:
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, pathOut);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
AVFormatContext中的unsigned int nb_streams
* Number of elements in AVFormatContext.streams.
*
* Set by avformat_new_stream(), must not be modified by any other code.
AVFormatContext 结构体中定义了AVStream **streams 数组;
nb_streams即为数组元素的个数
*
class A
{
public:
int a = 0;
};
int main()
{
A b;
A *p = &b;
b.a; //类类型的对象访问类的成员
p->a; //类类型的指针访问类的成员
}
*/
示例:
stream_mapping_size = ifmt_ctx->nb_streams;
av_mallocz_array()
av_alloc_size(1, 2) void *av_mallocz_array(size_t nmemb, size_t size);
创建int数组
* @param nmemb 数组元素的个数
* @param size 每个元素的内存长度
示例:
stream_mapping = static_cast<int *>(av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping)));
if (!stream_mapping) {
ret = AVERROR(ENOMEM);
goto end;
}
AVOutputFormat
ff_const59 struct AVOutputFormat *oformat;
输出文件的格式,只有在封装时使用,必须在调用avformat_write_header()前初始化
示例:
ofmt = ofmt_ctx->oformat;
typedef struct AVCodecParameters
这个结构体包含了编码流的属性
AVStream 中定义了 AVCodecParameters *codecpar;
当前流的编解码参数,
avformat_new_stream()调用后会初始化,
avformat_free_context()调用后会被释放。
解封装:在创建流的时候或者avformat_find_stream_info()调用后,被初始化;
封装:avformat_write_header()调用前,手动初始化
示例:
AVCodecParameters *in_codecpar = in_stream->codecpar;
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
enum AVMediaType
enum AVMediaType {
AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA
AVMEDIA_TYPE_VIDEO,
AVMEDIA_TYPE_AUDIO,
AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous
AVMEDIA_TYPE_SUBTITLE,
AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse
AVMEDIA_TYPE_NB
};
媒体类型
AVCodecParameters中定义了该属性,用于标志是音频还是视频、还是字幕
示例:
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping[i] = -1;
continue;
}
avformat_new_stream()
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
创建一个用于输出的AVStream指针对象
示例:
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
avcodec_parameters_copy
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
输出的AVCodecParameters指针所占内存被释放,
然后将输入的AVCodecParameters指针内存拷贝到输出的AVCodecParameters中
示例:
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
codecpar->codec_tag = 0疑问?
AVCodecParameters 中定义了uint32_t codec_tag;
* Additional information about the codec (corresponds to the AVI FOURCC).
uint32_t codec_tag;
为编解码器添加额外信息,这里懵逼了,这行不写,输出视频文件会有毛病
有知道原因的朋友可下方留言讨论
avio_open()
int avio_open(AVIOContext **s, const char *url, int flags);
为对应url的文件初始化一个AVIOContext 二级指针对象
AVIOContext
typedef struct AVIOContext
字节流IO Context,定义了一些有关IO读写的buffer缓冲区等属性
AVFormatContext中的AVIOContext *pb
解封装时,AVFormatContext中的AVIOContext *pb, 可以在调用avformat_open_input()之前初始化,
或者通过调用avformat_open_input()初始化
封装时,AVFormatContext中的AVIOContext *pb,可以在调用avformat_write_header()之前初始化,
完事后必须释放AVFormatContext中的AVIOContext *pb占用的内存
如果ofmt->flags值为AVFMT_NOFILE,就不要初始化AVFormatContext中的AVIOContext *pb,
在这种情况下,解封装器/封装器将会通过其它方式处理I/O,而且AVFormatContext中的AVIOContext *pb为NULL
示例:
if (!(ofmt->flags & AVFMT_NOFILE)) {
/**
* 为对应url的文件初始化一个AVIOContext 二级指针对象
*/
ret = avio_open(&ofmt_ctx->pb, pathOut, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'", pathOut);
goto end;
}
}
AVIO_FLAG_WRITE 写模式
/**
* @name URL open modes
* The flags argument to avio_open must be one of the following
* constants, optionally ORed with other flags.
* @{
*/
#define AVIO_FLAG_READ 1 /**< read-only */
#define AVIO_FLAG_WRITE 2 /**< write-only */
#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE) /**< read-write pseudo flag */
打开文件的模式,读模式,写模式,读写模式
avformat_write_header()
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
初始化流的私有数据并将流头写入输出媒体文件。
av_read_frame()
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
返回流的下一帧。
此函数读取存储在文件中的内容到AVPacket pkt,而不验证是否存在解码器的有效帧。它将存储在文件中的内容分割成帧,并为每个调用返回一个AVPacket pkt。它不会省略有效帧之间的无效数据,以便给解码器最大的解码信息。
返回0表示读取一帧成功,返回负数,表示出错了或者已经读到文件末尾了。
示例:
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
ffmpeg时间基相关详解
av_rescale_q_rnd()
int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
enum AVRounding rnd) av_const;
* Rescale a 64-bit integer by 2 rational numbers with specified rounding.
*
* The operation is mathematically equivalent to `a * bq / cq`.
*
* @see av_rescale(), av_rescale_rnd(), av_rescale_q()
以指定的取整方式,用2个有理数处理64位整数。
该运算在数学上等价于`a * bq/cq‘。
enum AVRounding
enum AVRounding {
AV_ROUND_ZERO = 0, ///< Round toward zero.
AV_ROUND_INF = 1, ///< Round away from zero.
AV_ROUND_DOWN = 2, ///< Round toward -infinity.
AV_ROUND_UP = 3, ///< Round toward +infinity.
AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases away from zero.
/**
* Flag telling rescaling functions to pass `INT64_MIN`/`MAX` through
unchanged, avoiding special cases for #AV_NOPTS_VALUE.
*
* Unlike other values of the enumeration AVRounding, this value is a
* bitmask that must be used in conjunction with another value of the
* enumeration through a bitwise OR, in order to set behavior for normal
* cases.
*
* @code{.c}
* av_rescale_rnd(3, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
* // Rescaling 3:
* // Calculating 3 * 1 / 2
* // 3 / 2 is rounded up to 2
* // => 2
*
* av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
* // Rescaling AV_NOPTS_VALUE:
* // AV_NOPTS_VALUE == INT64_MIN
* // AV_NOPTS_VALUE is passed through
* // => AV_NOPTS_VALUE
* @endcode
*/
AV_ROUND_PASS_MINMAX = 8192,
};
取整方式:
AV_ROUND_ZERO = 0,
趋近于0
AV_ROUND_INF = 1,
趋远于0
AV_ROUND_DOWN = 2,
向下取整
AV_ROUND_UP = 3,
向上取整
AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases away from zero.
四舍五入
AV_ROUND_PASS_MINMAX=8192
与枚举舍入的其他值不同,此值是一个位掩码,必须通过按位方式 (或),与枚举的另一个值一起使用。使用这个枚举值目的是为了避免#AV_NOPTS_VALUE情况下出错
AVStream中的AVRational time_base
这是用来表示显示的帧时间戳的基本时间单位(以秒为单位)。
解码:由libavmat设置
编码:可以由调用方在avformat_write_header()之前设置,以便向muxer提供有关所需时间基。在avformat_write_header()中,muxer将用实际用于写入文件的时间戳的时间基(根据格式可能与用户提供的时间戳相关,也可能与用户提供的时间戳无关)覆盖此字段。
用一把尺子做模型,time_base可理解为一个刻度表示多少秒
AVPacket中的int64_t pts
以AVStream->time_base时基单元表示显示给用户的时间戳;表示解压的数据包呈现给用户的时间。如果未存储在文件中,则pts为av_nopts_value。pts必须大于或等于dts,因为在解压缩之前不能出现表示,除非要查看十六进制转储。有些格式误用了术语DTS和PTS/CTS来表达不同的意思。这种时间戳必须转换为真正的PT/DTS,然后才能存储在AVPacket中。
用一把尺子做模型,time_base可理解为一个刻度表示多少秒,那么pts就表示占多少个刻度
示例:
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,
out_stream->time_base,static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.pts 乘 in_stream->time_base 除 out_stream->time_base
得到out_stream下pkt的pts,输出文件的一帧数据包的pts
即同步了输入输出的显示时间戳
pkt为从输入文件读取的一帧的数据包,
AVPacket中的int64_t dts
以AVStream->time_base时基单元表示解压的时间戳;解压数据包的时间。如果未存储在文件中,则dts为av_nopts_value。
用一把尺子做模型,time_base可理解为一个刻度表示多少秒,那么dts就表示占多少个刻度
示例:
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts 乘 in_stream->time_base 除 out_stream->time_base
得到out_stream下pkt的dts ,输出文件的一帧数据包的dts
即同步了输入输出的解压时间戳
pkt为从输入文件读取的一帧的数据包,
AVPacket中的int64_t duration
以AVStream->time_base时基单元表示此数据包的持续时间(当前帧的持续时间),如果未知则为0。
等价于在显示的顺序中,当前帧和下一帧的时间间隔。
用一把尺子做模型,time_base可理解为一个刻度表示多少秒,那么duration就表示占多少个刻度
示例:
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.duration乘 in_stream->time_base 除 out_stream->time_base
得到out_stream下pkt的duration ,输出文件的一帧数据包的持续时间
即同步了输入输出的持续时间戳
pkt为从输入文件读取的一帧的数据包,
av_rescale_q()
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
用2个有理数重新计算64位整数.这个运算在数学上等价于‘a*bq/cq’
。此函数等价于取整方式为四舍五入时的av_rescale_q_rnd()
————————————————
版权声明:以下摘抄为优快云博主「bixinwei」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/bixinwei22/article/details/78770090
PTS:Presentation Time Stamp。PTS
主要用于度量解码后的视频帧什么时候被显示出来
DTS:Decode Time Stamp。DTS
主要是标识读入内存中的bit
流在什么时候开始送入解码器中进行解码
也就是pts反映帧什么时候开始显示,dts反映数据流什么时候开始解码
怎么理解这里的“什么时候”呢?如果有某一帧,假设它是第10秒开始显示。那么它的pts是多少呢。是10?还是10s?还是两者都不是。
为了回答这个问题,先引入FFmpeg中时间基的概念,也就是time_base。它也是用来度量时间的。
如果把1秒分为25等份,你可以理解就是一把尺,那么每一格表示的就是1/25秒
。此时的time_base={1,25}
如果你是把1秒分成90000份,每一个刻度就是1/90000秒
,此时的time_base={1,90000}。
所谓时间基表示的就是每个刻度是多少秒
pts的值就是占多少个时间刻度(占多少个格子)。它的单位不是秒,而是时间刻度。只有pts加上time_base两者同时在一起,才能表达出时间是多少。
好比我只告诉你,某物体的长度占某一把尺上的20个刻度。但是我不告诉你,这把尺总共是多少厘米的,你就没办法计算每个刻度是多少厘米,你也就无法知道物体的长度。
pts=20个刻度
time_base={1,10} 每一个刻度是1/10厘米
所以物体的长度=`pts*time_base`=20*1/10 厘米
在ffmpeg中。av_q2d(time_base)
=每个刻度是多少秒
此时你应该不难理解 pts*av_q2d(time_base)
才是帧的显示时间戳。
下面理解时间基的转换,为什么要有时间基转换。
首先,不同的封装格式,timebase是不一样的。另外,整个转码过程,不同的数据状态对应的时间基也不一致。拿mpegts封装格式25fps来说(只说视频,音频大致一样,但也略有不同)。非压缩时候的数据(即YUV或者其它),在ffmpeg中对应的结构体为AVFrame,它的时间基为AVCodecContext 的time_base ,AVRational{1,25}
。
压缩后的数据(对应的结构体为AVPacket)对应的时间基为AVStream的time_base,AVRational{1,90000}
。
因为数据状态不同,时间基不一样,所以我们必须转换,在1/25时间刻度下占10格,在1/90000下是占多少格。这就是pts的转换。
根据pts来计算一桢在整个视频中的时间位置:
timestamp(秒) = `pts * av_q2d(st->time_base)`
duration和pts单位一样,duration表示当前帧的持续时间占多少格。或者理解是两帧的间隔时间是占多少格。一定要理解单位。
pts:格子数
`av_q2d(st->time_base)`: 秒/格
计算视频长度:
time(秒) = st->duration * av_q2d(st->time_base)
ffmpeg内部的时间与标准的时间转换方法:
ffmpeg内部的时间戳 = AV_TIME_BASE * time(秒)
AV_TIME_BASE_Q=1/AV_TIME_BASE
av_rescale_q(int64_t a, AVRational bq, AVRational cq)
函数
这个函数的作用是计算a*bq / cq来把时间戳从一个时间基调整到另外一个时间基。在进行时间基转换的时候,应该首先这个函数,因为它可以避免溢出的情况发生。
函数表示在bq下的占a个格子,在cq下是多少。
关于音频pts的计算:
音频sample_rate:samples per second,即采样率,表示每秒采集多少采样点。
比如44100HZ
,就是一秒采集44100个sample.
即每个sample的时间是1/44100秒
一个音频帧的AVFrame有nb_samples个sample,所以一个AVFrame耗时是nb_samples*(1/44100)秒
即标准时间下duration_s=nb_samples*(1/44100)
秒,
转换成AVStream时间基下
duration=duration_s / av_q2d(st->time_base)
基于st->time_base的num值一般等于采样率,所以duration=nb_samples.
pts=n*duration=n*nb_samples
补充:
next_pts-current_pts=current_duration,根据数学等差公式an=a1+(n-1)d可得pts=nd
av_interleaved_write_frame()
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
将一帧数据包写入输出媒体文件。
此函数将根据需要在内部缓冲数据包,以确保输出文件中的数据包按照dts的顺序正确交叉存储。调用者进行自己的交叉存储时,应该调用av_write_frame(),而不是这个函数。使用此函数而不是av_write_frame()可以使muxers提前了解未来的数据包,例如改善MP4对VFR内容在碎片模式下的行为。
Interleaving(交叉存取技术)
加快内存速度的一种技术。举例来说,将存储体的奇数地址和偶数地址部分分开,这样当前字节被刷新时,可以不影响下一个字节的访问。
交叉存储主要补偿DRAM等存储器相对较慢的读写速度。读或写每一个内存块,都需要等待内存块给出ready信号,才能读写下一字节。交叉存储将连续信息分散到各个块中,读写时可以同时等待多个内存块给出ready信号,从而提高了读写的速度。
av_write_frame()
int av_write_frame(AVFormatContext *s, AVPacket *pkt);
将一帧数据包写入输出媒体文件。此函数直接将数据包传递给muxer,而不需要任何缓冲或重新排序。如果格式需要,调用方负责正确地交叉存储数据包。希望libavmat处理交叉存储的调用者应该调用av_interleved_write_frame(),而不是这个函数。
欢迎联系、指正、批评
Github:https://github.com/AnJiaoDe
优快云:https://blog.youkuaiyun.com/confusing_awakening
OpenCV入门教程:https://blog.youkuaiyun.com/confusing_awakening/article/details/113372425
ffmpeg入门教程:https://blog.youkuaiyun.com/confusing_awakening/article/details/102007792