解封装跟封装一块讨论,更容易理解,也更好调试。
解封装:一个视频文件,比如mp4,解封装后,可以得到具体的流AVStream(视频流,音频流,文字流等)
封装:把各种流组成一个视频文件
所以你可以看到demo代码总体逻辑为:先解封装,得到各种流后,再根据这些流封装成视频文件。
代码参考:http://ffmpeg.org/doxygen/trunk/remuxing_8c-example.html
头文件:
#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
比较懒,全部的头文件都加进来了
主要代码:
- (int)mainFunc{
av_register_all();
avcodec_register_all();
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
const char *in_filename, *out_filename;
int ret, i;
int stream_index = 0;
int *stream_mapping = NULL;
int stream_mapping_size = 0;
//input
NSString *filePath = [CommonFunc getDocumentWithFile:@"movie.mp4"];
in_filename = [filePath cStringUsingEncoding:NSASCIIStringEncoding];
//output
filePath = [CommonFunc getDefaultPath:@"movieOut.mp4"];
out_filename = [filePath cStringUsingEncoding:NSASCIIStringEncoding];
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;
}
stream_mapping_size = ifmt_ctx->nb_streams;
stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
if (!stream_mapping) {
ret = AVERROR(ENOMEM);
goto end;
}
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams[I];
AVCodecParameters *in_codecpar = in_stream->codecpar;
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++;
//这里创建的流,默认是从0开始累加的,所以不一定跟ifmt_ctx流的位置对应
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
// out_stream->codecpar->codec_tag = 0;
//可以在avformat_write_header配置好time_base,要不然执行完avformat_write_header后,就会有默认的time_base,因为两个流的time_base不一样,所以后面的转换设置pkt.pts pkt.dts pkt.duration。time_base一样就不需要设置
out_stream->time_base = in_stream->time_base;
}
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;
}
}
//执行这个后,out_stream->time_base修改为对应格式的默认值,比如mp4,默认为90k
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;
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
if (pkt.stream_index >= stream_mapping_size ||
stream_mapping[pkt.stream_index] < 0) {
av_packet_unref(&pkt);
continue;
}
pkt.stream_index = stream_mapping[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
// out_stream->time_base = in_stream->time_base;
log_packet(ifmt_ctx, &pkt, "in");
/* copy packet */
// pkt.pts = av_rescale_q_rnd(pkt.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, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
// pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
// pkt.pos = -1;
log_packet(ofmt_ctx, &pkt, "out");
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_packet_unref(&pkt);
}
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);
av_freep(&stream_mapping);
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
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);
}
解封装部分:
AVFormatContext *ifmt_ctx = NULL;
avformat_open_input(&ifmt_ctx, in_filename, 0, 0)
avformat_find_stream_info(ifmt_ctx, 0)
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
//得到了各个流
AVStream *in_stream = ifmt_ctx->streams[I];
}
解封装就这么简单。
可以根据in_stream->codecpar->codec_type判断具体的流的类型,之前版本使用in_stream->codec,最新的提示弃用了这个结构,改为in_stream->codecpar,当然现在你也还是可以用in_stream->codec的。其实in_stream->codec就是之前说的AVCodecContext,记录编解码的一些信息。没有它,就算得到了流,也没意义,因为无法具体去解码。
封装部分:
//创建输出的封装上下文
AVFormatContext *ofmt_ctx = NULL
//根据输出文件名,得到具体的ofmt_ctx内容
//当然这里得到的只是很少的内容
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
//从ofmt_ctx创建流,流的index是从0开始累加的
out_stream = avformat_new_stream(ofmt_ctx, NULL);
//从对应的输入流中拷贝一份codecpar给对应的输出流
//这就确定了,这是什么类型的流(视频流,音频流等)
avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
//time_base也用输入流的,每个流都有它对应的time_base,之前有讨论过time_base的意思
out_stream->time_base = in_stream->time_base;
//下面都是具体的输出操作
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE)
avformat_write_header(ofmt_ctx, NULL)
av_interleaved_write_frame(ofmt_ctx, &pkt)
av_write_trailer(ofmt_ctx)
存在于流中的,代表具体的编码的数据内容的是AVPacket结构体
AVPacket存储的是编码后的数据,如果是mp4,就有可能存的是h264编码数据
所以你看代码:
AVPacket pkt;
av_read_frame(ifmt_ctx, &pkt)
从输入流读出来的是pkt。这里也好理解。
然后就是把pkt写入到输出ofmt_ctx 的对应的流中:
av_interleaved_write_frame(ofmt_ctx, &pkt)
那么,我怎么知道写入到哪个流?
根据pkt.stream_index值,就知道是写入哪个流了
代码还用到了stream_mapping,这个不是主要代码,就是为了用输入流的index找到对应的新创的输出流的index,因为他们的index不一定相等。
还有很有意思的,无关要紧的一段输出log代码:
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);
这里面还是包含了不少信息,我简单讲讲:
pkt->pts:可以认为是帧的展示时间,就是在显示器的显示的时间
pkt->dts:帧的解码时间,为什么要加入这个呢,不是根据pts时间,然后同步解码就可以啦。其实有些帧是要根据前后的帧才可以解码的,如B帧,那么得到B帧后,是不是得先去解B帧后面的帧。
pkt->duration:帧的时长
time_base:pts、dts、duration的时间单位,之前有讲到过
截取了段输出内容:
屏幕快照 2019-04-15 下午8.24.37.png
pts:1024 然后转化为秒就是0.0666667,具体算法是:pts*timebase = 1024*(1/15360)= 0.0666667
就是说着一帧在0.0666667的时候播放出来,那么后面我们就可以通过控制timebase来决定pts了,那么就相当于控制了视频的播放速度,总时长等。
最后别忘了进行资源的释放与文件的关闭。
网友评论