我们知道多媒体文件是由多个stream组成,每个stream对应不同的内容。比如视频,音频,字幕。
如果要用A视频的图像,B视频的音乐,生成C视频。原理就是提取出A视频的视频流数据,B视频的的音频流数据,写入C视频的视频流和音频流中。
流程图如下
#include <stdlib.h>
#include <libavutil/log.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libavcodec/codec_par.h>
int make_out_file(double start_time, double end_time, char *audio_file_name, char *video_file_name, char *out_file_name);
int main(int argc, char *argv[])
{
//输入4个参数 argv1 开始时间 argv2 结束时间 argv3 输入文件 argv3 输出文件
double start_time, end_time;
char *audio_file_name, *video_file_name, *out_file_name;
av_log_set_level(AV_LOG_INFO);
if (argc < 6)
{
av_log(NULL, AV_LOG_ERROR, "输入参数错误");
return -1;
}
start_time = atoi(argv[1]);
end_time = atoi(argv[2]);
audio_file_name = argv[3];
video_file_name = argv[4];
out_file_name = argv[5];
int ret = make_out_file(start_time, end_time, audio_file_name, video_file_name, out_file_name);
return ret;
}
AVFormatContext *open_input_file(char *file_name)
{
int ret = 0;
AVFormatContext *fmt_ctx = NULL;
ret = avformat_open_input(&fmt_ctx, file_name, NULL, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "打开输入文件失败 %s", file_name);
return fmt_ctx;
}
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "获取输入文件流信息失败 %s", file_name);
return fmt_ctx;
}
av_dump_format(fmt_ctx, 0, file_name, 0);
return fmt_ctx;
}
int createStream(AVFormatContext *ofmt_ctx, AVStream *in_stream)
{
AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
int ret = 0;
if (!out_stream)
{
av_log(NULL, AV_LOG_ERROR, "创建输出流失败");
return -1;
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "copy 流参数错误");
return ret;
}
out_stream->codecpar->codec_tag = 0;
return ret;
}
int write_data(AVFormatContext* in_fmt_ctx, int in_index,
AVFormatContext* out_fmt_ctx, int out_index,
double start_time, double end_time){
AVPacket pkt;
av_init_packet(&pkt);
pkt.size = 0;
pkt.data = NULL;
int base_pts = 0;
int base_dts = 0;
int ret = 0;
ret = av_seek_frame(in_fmt_ctx, -1, start_time * AV_TIME_BASE, AVSEEK_FLAG_ANY);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "移动的上下文到指定时间失败");
return ret;
}
//写音频
while (1)
{
AVStream *in_stream, *out_stream;
ret = av_read_frame(in_fmt_ctx, &pkt);
if (ret < 0)
break;
if (pkt.stream_index != in_index)
{
av_packet_unref(&pkt);
continue;
}
in_stream = in_fmt_ctx->streams[in_index];
out_stream = out_fmt_ctx->streams[out_index];
//如果当前时间超过了要截取的时间,直接跳出循环
if (av_q2d(in_stream->time_base) * pkt.pts > end_time)
{
av_packet_unref(&pkt);
break;
}
//设置输出文件的dts和pts
// av_log(NULL, AV_LOG_INFO, "writefreame pkt.pts = %lld\n", pkt.pts);
if (base_pts == 0 || base_dts == 0)
{
base_dts = base_pts = pkt.pts;
av_log(NULL, AV_LOG_INFO, "writefreame base_pts = %d\n", base_pts);
}
av_log(NULL, AV_LOG_INFO, "writefreame pkt.pts = %lld, dts = %lld\n", pkt.pts, pkt.dts);
pkt.pts = av_rescale_q_rnd(pkt.pts - base_pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts - base_dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt.duration = (int)av_rescale_q((int64_t)pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
pkt.stream_index = out_index;
av_log(NULL, AV_LOG_INFO, "writefreame 22222 pkt.pts = %lld, dts = %lld\n", pkt.pts, pkt.dts);
ret = av_write_frame(out_fmt_ctx, &pkt);
av_packet_unref(&pkt);
if (ret < 0)
break;
}
return ret;
}
int make_out_file(double start_time,
double end_time,
char *audio_file_name,
char *video_file_name,
char *out_file_name)
{
int ret = 0;
AVFormatContext *i_audio_fmt_ctx = NULL, *i_video_fmt_ctx = NULL, *ofmt_ctx = NULL;
//第一步获取输入上下文
i_audio_fmt_ctx = open_input_file(audio_file_name);
if (!i_audio_fmt_ctx)
{
ret = -1;
goto __ERROR;
}
i_video_fmt_ctx = open_input_file(video_file_name);
if (!i_audio_fmt_ctx)
{
ret = -1;
goto __ERROR;
}
//第二步获取输出上下文
ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_file_name);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "获取输出文件上下文失败");
goto __ERROR;
}
int video_stream_index = av_find_best_stream(i_video_fmt_ctx,
AVMEDIA_TYPE_VIDEO,
-1,
-1,
NULL,
-1);
av_log(NULL, AV_LOG_ERROR, "输入文件的视频频流索引是 videoStreamIndex=%d", video_stream_index);
int audio_stream_index = av_find_best_stream(i_audio_fmt_ctx,
AVMEDIA_TYPE_AUDIO,
-1,
-1,
NULL,
-1);
av_log(NULL, AV_LOG_ERROR, "输入文件的视频频流索引是 videoStreamIndex=%d", audio_stream_index);
//第三步为输出文件创建输出流
int out_video_stream_index = -1;
int out_audio_stream_index = -1;
ret = createStream(ofmt_ctx, i_video_fmt_ctx->streams[video_stream_index]);
if(ret < 0){
goto __ERROR;
}
out_video_stream_index = 0;
ret = createStream(ofmt_ctx, i_audio_fmt_ctx->streams[audio_stream_index]);
if(ret < 0){
goto __ERROR;
}
out_audio_stream_index = 1;
av_dump_format(ofmt_ctx, 0, out_file_name, 1);
//第四步为输出文件创建AvIOContext
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
{
ret = avio_open(&(ofmt_ctx->pb), out_file_name, AVIO_FLAG_WRITE);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "创建输出文件IO上下文失败");
goto __ERROR;
}
}
//第五步写输出文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "写多媒体文件头失败");
goto __ERROR;
}
//第六步读取指定时间段的数据,然后进行时间戳转化,再写入输出文件
ret = write_data(i_video_fmt_ctx, video_stream_index,
ofmt_ctx, out_video_stream_index,
start_time, end_time);
if (ret < 0)
{
goto __ERROR;
}
ret = write_data(i_audio_fmt_ctx, audio_stream_index,
ofmt_ctx, out_audio_stream_index,
start_time, end_time);
if (ret < 0)
{
goto __ERROR;
}
//第七步写多媒体文件尾巴
av_write_trailer(ofmt_ctx);
__ERROR:
//第八步释放资源
if (i_audio_fmt_ctx)
{
avformat_close_input(&i_audio_fmt_ctx);
avformat_free_context(i_audio_fmt_ctx);
}
if (i_video_fmt_ctx)
{
avformat_close_input(&i_video_fmt_ctx);
avformat_free_context(i_video_fmt_ctx);
}
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
{
avio_closep(&(ofmt_ctx->pb));
}
if (ofmt_ctx)
{
avformat_free_context(ofmt_ctx);
}
return ret;
}
坚持输出,才能不停的输入。
学习基础,才能学得更深。
学习要慢点再慢点。
学习原理,对抗碎片。