ffmpeg的一些基础和流程,记录下,加深理解。
1 首先来说,重要的结构体
AVFormatContext 环境上下文,对每个媒体或者网络媒体环境的管理类
AVCodecContext 编解码上下文,某个流(音频或视频)的上下文环境,记录媒体信息
AVCodec 具体编解码器,编解码器,用于媒体编解码
AVPacket 编码包
AVFrame 解码包
2 公共的一些函数
av_register_all() 注册的接口
avfilter_register_all() 过滤器的一些接口
avformat_network_init() 网络上下文注册接口
avdevice_register_all() 设备环境上下文注册
av_log_set_level() 日志级别
3 输入部分(一般表示渲染之前流程)
avformat_alloc_context 申请上下文的环境,ffmpeg的申请环境。
av_find_input_format 根据名称打开输入上下文,windows下一般都是通过dshow来查找相关的上下文输入环境
avfoundation用于苹果系统的上下文,alsa gdigrab x11grab linux上下文 还有其他一些上下文
bktr dv1394 fbdev jack openal oss pulse video4linux2 ,v4l2 vfwcap decklink
avformat_open_input 打开输入环境上下文,根据av_find_input_format ,根据查找到的输入媒体格式,打开上下文 ,通过
avformat_close_input来关闭,第一个参数为返回的输入环境上下文,第二个参数定义了具体的网络url
设备url 比如 "audio=dummy" "video=HP Camera" "rtmp://helloworld/"等,第三个参数是通过上面函数
找到的输入设备格式,第四个参数AVDictionary 通过此变量设置的一些参数,通过av_dict_set_int
av_dict_set 设置的输入参数,比如"video_size 1024x768" "probesize 100 * 1024" "pixel_format nv12"
"framerate 30"等。
avformat_find_stream_info 读取一些包,然后从中提取流的信息,有一些文件格式没有头,比如MPEG格式的,这个时候,这个函数就很有用
他可以从读取到的包中得到流的信息,还可以读出流的帧率,而读出来的包会被缓存起来以供处理
avcodec_find_decoder 通过codecID来查找解码器,或者可以通过avcodec_find_decoder_by_name()也可以通过name查找,一般是在avstream中
查找解码器
avcodec_open2 打开解码器 ,根据解码上下文,来打开编码器
av_read_frame 从解码器中读取一帧,放在AVPacket中
avcodec_decode_video2 解码视频
avcodec_decode_audio4 解码音频
avcodec_send_packet 新版本的解码,先将AVPacket放入然后通过avcodec_receive_frame得到解码的帧
avcodec_receive_frame
avformat_close_input 关闭编码环境
4 输出部分(一般表示采集过程)
avformat_alloc_output_context2 用于初始化一个用于输出的环境上下文。首先通过avformat_alloc_context();
申请环境上下文,然后通过传递的format_name(输出格式的名称)av_guess_format
获取AVOutputFormat,然后根据filename输出文件的名称得到环境上下文
avio_open2 打开输入输出文件,调用成功后创建AVIOContext,在URLProtocol得到协议读写的函数指针
可以读写网络媒体流
avcodec_find_encoder 用于查找编码环境
avcodec_alloc_context3 用于申请编码上下文
avcodec_open2 打开编码器
avformat_new_stream 创建stream通道,即创建输出码流的AVStream
avformat_copy_context --> avcodec_find_decoder + avcodec_alloc_context3 + avcodec_parameters_to_context
+ avcodec_parameters_from_context
avformat_write_header 根据输出文件写文件头。
avcodec_encode_video2 编码媒体流
avcodec_send_frame 新版本的编码媒体流,输入frame,得到packet
avcodec_receive_packet
av_interleaved_write_frame 写入媒体帧
av_write_trailer 写入输出文件的文件尾
avcodec_close 关闭编码器
avformat_close_input 关闭环境上下文
5 示例代码
#include <stdio.h>
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 cut_video(double from_seconds, double end_seconds, const char* in_filename, const char* out_filename) {
定义的变量,输出环境上下文,输入环境上下文,编码包,函数调用返回值,帧循环计数
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
int ret, i;
av_register_all();
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '%s'", in_filename);
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
//打印输入和输出格式的详细信息
av_dump_format(ifmt_ctx, 0, in_filename, 0);
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
/* AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0) {
fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
*/
AVStream *in_stream = ifmt_ctx->streams[i];
AVCodec * codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
AVStream *out_stream = avformat_new_stream(ofmt_ctx, codec);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
AVCodecContext * codec_ctx = avcodec_alloc_context3(codec);
ret = avcodec_parameters_to_context(codec_ctx, in_stream->codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy in_stream codecpar to codec context\n");
goto end;
}
codec_ctx->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec context to out_stream codecpar context\n");
goto end;
}
}
av_dump_format(ofmt_ctx, 0, out_filename, 1);
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'", out_filename);
goto end;
}
}
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
//按视频定位媒体
// int64_t start_from = 8*AV_TIME_BASE;
ret = av_seek_frame(ifmt_ctx, -1, from_seconds*AV_TIME_BASE, AVSEEK_FLAG_ANY);
if (ret < 0) {
fprintf(stderr, "Error seek\n");
goto end;
}
int64_t *dts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
memset(dts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);
int64_t *pts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
memset(pts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);
while (1) {
AVStream *in_stream, *out_stream;
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
log_packet(ifmt_ctx, &pkt, "in");
if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
av_free_packet(&pkt);
break;
}
if (dts_start_from[pkt.stream_index] == 0) {
dts_start_from[pkt.stream_index] = pkt.dts;
printf("dts_start_from: %s\n", av_ts2str(dts_start_from[pkt.stream_index]));
}
if (pts_start_from[pkt.stream_index] == 0) {
pts_start_from[pkt.stream_index] = pkt.pts;
printf("pts_start_from: %s\n", av_ts2str(pts_start_from[pkt.stream_index]));
}
/* copy packet */
pkt.pts = av_rescale_q_rnd(pkt.pts - pts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts - dts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
if (pkt.pts < 0) {
pkt.pts = 0;
}
if (pkt.dts < 0) {
pkt.dts = 0;
}
pkt.duration = (int)av_rescale_q((int64_t)pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
log_packet(ofmt_ctx, &pkt, "out");
printf("\n");
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
//break; // 注释后,解决 错误 pts (6000) < dts (12000) in stream 0
}
av_free_packet(&pkt);
}
free(dts_start_from);
free(pts_start_from);
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_closep(&ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
///测试函数,实现了从源文件读出写入目的文件,输入开始时间和结束时间以及输入文件和输出文件名
int test(const char *starttime,const char *endtime,const char *srcfile,const char *outfile){
double startime = atoi(starttime);
double endtime = atoi(endtime);
cut_video(startime, endtime, srcfile, endfile);
return 0;
}
6 上述剪切出现问题,由于pts和dts不同步导致异常写入
所以在写入的时候,需要判断如果 if(packet.pts < packet.dts) continue; 即将B帧解码略过