最简单的基于 FFmpeg 的封装格式转换器(无编解码)
- 最简单的基于 FFmpeg 的封装格式转换器(无编解码)
- 正文
- 结果
- 工程文件下载
最简单的基于 FFmpeg 的封装格式转换器(无编解码)
参考雷霄骅博士的文章,链接:最简单的基于FFMPEG的封装格式转换器(无编解码)
正文
本文介绍一个最简单的基于 FFmpeg 的封装格式转换器。所谓的封装格式转换,就是在 AVI,FLV,MKV,MP4 这些格式之间转换(对应 .avi,.flv,.mkv,.mp4 文件)。
需要注意的是,本程序并不进行视音频的编码和解码工作。而是直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。
传统的转码程序工作原理如下图所示:
上图例举了一个举例:FLV(视频:H.264,音频:AAC)转码为 AVI(视频:MPEG2,音频:MP3)的例子。可见视频转码的过程通俗地讲相当于把视频和音频重新“录”了一遍。
本程序的工作原理如下图所示:
由图可见,本程序并不进行视频和音频的编解码工作,因此本程序和普通的转码软件相比,有以下两个特点:
- 处理速度极快。视音频编解码算法十分复杂,占据了转码的绝大部分时间。因为不需要进行视音频的编码和解码,所以节约了大量的时间。
- 视音频质量无损。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。
下面附上基于 FFmpeg 的 Remuxer 的流程图。图中使用浅红色标出了关键的数据结构,浅蓝色标出了输出视频数据的函数。可见整个程序包含了对两个文件的处理:读取输入文件(位于左边)和写入输出文件(位于右边)。中间使用了一个 avcodec_copy_context() 拷贝输入的 AVCodecContext 到输出的 AVCodecContext。
简单介绍一下流程中关键函数的意义:
输入文件操作:
-
avformat_open_input():打开输入文件,初始化输入视频码流的 AVFormatContext。
-
av_read_frame():从输入文件中读取一个 AVPacket。
输出文件操作:
-
avformat_alloc_output_context2():初始化输出视频码流的 AVFormatContext。
-
avformat_new_stream():创建输出码流的 AVStream。
-
avcodec_copy_context():拷贝输入视频码流的 AVCodecContex 的数值 t 到输出视频的 AVCodecContext。
-
avio_open():打开输出文件。
-
avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说 MPEG2TS)。
-
av_interleaved_write_frame():将 AVPacket(存储视频压缩码流数据)写入文件。
-
av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说 MPEG2TS)。
源代码:
// Simplest FFmpeg Remuxer.cpp : 定义控制台应用程序的入口点。
//
/*
* 最简单的基于FFmpeg的封装格式转换器
* Simplest FFmpeg Remuxer
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本程序实现了视频封装格式之间的转换。
* 需要注意的是本程序并不改变视音频的编码格式。
*
* This software converts a media file from one container format
* to another container format without encoding/decoding video files.
*/
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{
// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
// Windows
extern "C"
{
#include "libavformat/avformat.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif
int main(int argc, char* argv[])
{
AVOutputFormat *ofmt = NULL;
// 输入对应一个 AVFormatContext,输出对应一个 AVFormatContext
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
const char *in_filename = "cuc_ieschool.flv"; // 输入文件名
const char *out_filename = "cuc_ieschool.mp4"; // 输出文件名
int ret;
int frame_index;
int i;
//if (argc < 3)
//{
// printf("usage: %s input output\n"
// "Remux a media file with libavformat and libavcodec.\n"
// "The output format is guessed according to the file extension.\n"
// , argv[0]);
// return 1;
//}
av_register_all();
// 输入
ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
if (ret < 0)
{
printf("Could not open input file.\n");
goto end;
}
ret = avformat_find_stream_info(ifmt_ctx, 0);
if (ret < 0)
{
printf("Failed to retrieve input stream information.\n");
goto end;
}
// Print some input information
av_dump_format(ifmt_ctx, 0, in_filename, 0);
// 输出
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (ofmt_ctx == NULL)
{
printf("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 == NULL)
{
printf("Failed allocating output stream.\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// 拷贝输入视频码流的 AVCodecContex 的数值 t 到输出视频的 AVCodecContext
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0)
{
printf("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 |= CODEC_FLAG_GLOBAL_HEADER;
}
}
// Print some output information
av_dump_format(ofmt_ctx, 0, out_filename, 1);
// Open output file
if (!(ofmt->flags & AVFMT_NOFILE))
{
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0)
{
printf("Could not open output file '%s'.\n", out_filename);
goto end;
}
}
// Write file header
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0)
{
printf("Error occurred when opening output file.\n");
goto end;
}
frame_index = 0;
while (1)
{
AVStream *in_stream, *out_stream;
// 获取一个 AVPacket
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];
// copy packet
// 转换 PTS/DTS
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(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;
// 将 AVPacket 写入文件
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0)
{
printf("Error muxing packet.\n");
break;
}
printf("Write %8d frames to output file.\n", frame_index);
av_free_packet(&pkt);
frame_index++;
}
// Write file trailer
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
// close output
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
{
avio_close(ofmt_ctx->pb);
}
avformat_free_context(ofmt_ctx);
if (ret < 0 && ret != AVERROR_EOF)
{
printf("Error occurred.\n");
return -1;
}
system("pause");
return 0;
}
本程序可以直接在 VS 2015 上运行。
结果
运行程序,将输入 flv 文件转封装为 mp4 文件,以下是其信息:
下图显示了一个测试的输入文件的视音频参数。
下图显示了输出文件的视音频参数。
可以看出除了视频的封装格式从flv转换成了mp4,其他有关视音频编码的参数没有任何变化。
工程文件下载
GitHub:UestcXiye / Simplest-FFmpeg-Remuxer
CSDN:Simplest FFmpeg Remuxer.zip