文章目录

  • 最简单的基于FFMPEG 4.2的封装格式转换器(无编解码)
  • 配置
  • 代码
  • 结果
  • 关键函数说明
    • avformat_open_input
  • avformat_find_stream_info
    • av_dump_format
    • avformat_alloc_output_context2
    • av_mallocz_array
    • avformat_new_stream 创建新的流
    • avcodec_parameters_copy
    • avio_open
    • avformat_write_header
    • av_read_frame
    • av_packet_unref
    • av_rescale_q_rnd
    • av_rescale_q
    • av_interleaved_write_frame
    • av_write_trailer
  • 参考资料

最简单的基于FFMPEG 4.2的封装格式转换器(无编解码)

本文介绍一个基于 FFMPEG 的封装格式转换器。所谓的封装格式转换,就是在 AVI,FLV,MKV,MP4 这些格式之间转换(对应. avi,.flv,.mkv,.mp4 文件)。需要注意的是,本程序并不进行视音频的编码和解码工作。而是直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。传统的转码程序工作原理如下图所示:

上图例举了一个举例:FLV(视频:H.264,音频:AAC)转码为AVI(视频:MPEG2,音频MP3)的例子。可见视频转码的过程通俗地讲相当于把视频和音频重新“录”了一遍。
本程序的工作原理如下图所示:

由图可见,本程序并不进行视频和音频的编解码工作,因此本程序和普通的转码软件相比,有以下两个特点:
处理速度极快。视音频编解码算法十分复杂,占据了转码的绝大部分时间。因为不需要进行视音频的编码和解码,所以节约了大量的时间。
视音频质量无损。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。

配置


avcodec.lib
avdevice.lib
avfilter.lib
avformat.lib
avutil.lib
postproc.lib
swresample.lib
swscale.lib

代码

大部分代码参考FFmpeg/example下的remuxing.c文件,这里改为了cpp文件。

#include <iostream>
#include <thread>/*** @file* libavformat/libavcodec demuxing and muxing API example.* libavformat/libavcodec解编和复用API示例** Remux streams from one container format to another.* 将流从一种容器格式重新传输到另一种容器** @example remuxing.c*/extern "C" {#include <libavutil/timestamp.h>#include <libavformat/avformat.h>
}char av_ts_string[AV_TS_MAX_STRING_SIZE] = { 0 };
#define av_ts2str(ts) av_ts_make_string(av_ts_string, ts)char av_ts_buff[AV_TS_MAX_STRING_SIZE] = { 0 };
#define av_ts2timestr(ts, tb) av_ts_make_time_string(av_ts_buff, ts, tb)char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum) av_make_error_string(av_error, AV_ERROR_MAX_STRING_SIZE, errnum)//----------打印文件信息----------start----------
void msg(const char* str, int ret)
{static char err[512];if (ret < 0){av_strerror(ret, err, 1024);printf("%s error: %s\n", str, err);exit(ret);}else{printf("%s : success.\n", str);}
}void test_stream_info(const char* input_filename)
{const char* filename = input_filename;AVFormatContext* pFormatCtx = avformat_alloc_context();msg("avformat_open_input", avformat_open_input(&pFormatCtx, filename, nullptr, nullptr));msg("avformat_find_stream_info", avformat_find_stream_info(pFormatCtx, nullptr));av_dump_format(pFormatCtx, 0, filename, 0);avformat_close_input(&pFormatCtx);avformat_free_context(pFormatCtx);
}
//----------打印文件信息----------end----------//----------MP4转FLV----------start----------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 test_remuxing() {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;//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;//}//in_filename  = argv[1];//out_filename = argv[2];in_filename = "D:\\javaCode\\androidVideo2022\\368_384.mp4";out_filename = "D:\\javaCode\\androidVideo2022\\368_384.flv";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 = (int*)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++;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;}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;}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];log_packet(ifmt_ctx, &pkt, "in");/* copy packet */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;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;
}
//----------MP4转FLV----------end----------//最简单的基于FFMPEG的封装格式转换器
int main(int argc, char** argv)
{//一 ,打印MP4文件信息const char* in_filename, * out_filename;in_filename  = "D:\\javaCode\\androidVideo2022\\368_384.mp4";out_filename = "D:\\javaCode\\androidVideo2022\\368_384.flv";//test_stream_info(in_filename);//二 ,把MP4解封装转为FLV//test_remuxing();//三 ,打印FLV文件信息test_stream_info(out_filename);return 0;}

结果

关键函数说明

avformat_open_input

/**Open an input stream and read the header. The codecs are not opened.The stream must be closed with avformat_close_input().打开输入流并读取标头。编解码器未打开。流必须用avformat_close_input关闭*/
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

avformat_find_stream_info

/**Read packets of a media file to get stream information. Thisis useful for file formats with no headers such as MPEG. Thisfunction also computes the real framerate in case of MPEG-2 repeatframe mode.读取媒体文件的数据包以获取流信息。这对于没有标头的文件格式(如MPEG)很有用。此函数还计算MPEG-2重复帧模式下的实际帧速率The logical file position is not changed by this function;examined packets may be buffered for later processing.此功能不会更改逻辑文件位置;被检查的数据包可以被缓冲以供以后处理*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

av_dump_format

/**Print detailed information about the input or output format, such asduration, bitrate, streams, container, programs, metadata, side data,codec and time base.打印有关输入或输出格式的详细信息,例如持续时间、比特率、流、容器、程序、元数据、副数据、编解码器和时基@param ic        the context to analyze@param index     index of the stream to dump information about@param url       the URL to print, such as source or destination file@param is_output Select whether the specified context is an input(0) or output(1)*/
void av_dump_format(AVFormatContext *ic, int index,const char *url, int is_output);

avformat_alloc_output_context2

/**Allocate an AVFormatContext for an output format.为输出格式分配AVFormatContextavformat_free_context() can be used to free the context andeverything allocated by the framework within it.avformat_free_context()可用于释放上下文和框架在其中分配的所有内容@param *ctx is set to the created format context, or to NULL incase of failure@param oformat format to use for allocating the context, if NULLformat_name and filename are used instead@param format_name the name of output format to use for allocating thecontext, if NULL filename is used instead@param filename the name of the filename to use for allocating thecontext, may be NULL@return >= 0 in case of success, a negative AVERROR code in case offailure*/
int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat,const char *format_name, const char *filename);

av_mallocz_array

//使用av_mallocz为阵列分配内存块
av_alloc_size(1, 2) void *av_mallocz_array(size_t nmemb, size_t size);

函数av_mallocz_arrayav_malloc_array 唯一区别在于内部分配内存函数调用 av_mallocz 实现,也就是分配的内存块会清零。

此函数比 av_malloc 多一个动作,会将分配块的所有字节置零。这也是命名多加一个“z”的含义,zero 的意思。

看源码确实也是在调用 av_malloc 后,又接了 memset 置 0 操作。
libavutil/mem.c

void *av_mallocz(size_t size)
{void *ptr = av_malloc(size);if (ptr)memset(ptr, 0, size);return ptr;
}

avformat_new_stream 创建新的流

/**Add a new stream to a media file.将新流添加到媒体文件When demuxing, it is called by the demuxer in read_header(). If theflag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may alsobe called in read_packet().当demuxing时,它由read_header()中的demuxer调用。如果标志AVFMTCTX_NOHEADER在s.ctx_flags中设置,那么它也可以在read_packet中调用When muxing, should be called by the user before avformat_write_header().当muxing时,应在avformat_write_header之前由用户调用User is required to call avcodec_close() and avformat_free_context() toclean up the allocation by avformat_new_stream().用户需要调用avcodec_close()和avformat_free_context()来清理avformat_new_stream的分配*/
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

avcodec_parameters_copy

/**Copy the contents of src to dst. Any allocated fields in dst are freed andreplaced with newly allocated duplicates of the corresponding fields in src.将src的内容复制到dst。dst中的任何分配字段都将被释放,并替换为src中相应字段的新分配副本@return >= 0 on success, a negative AVERROR code on failure.*/
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);

avio_open

/**Create and initialize a AVIOContext for accessing theresource indicated by url.创建并初始化AVIOContext,用于访问url指示的资源*/
int avio_open(AVIOContext **s, const char *url, int flags);

avformat_write_header

/**Allocate the stream private data and write the stream header toan output media file.分配流私有数据并将流头写入输出媒体文件*/
int avformat_write_header(AVFormatContext *s, AVDictionary **options);

av_read_frame

/**Return the next frame of a stream.返回流的下一帧This function returns what is stored in the file, and does not validatethat what is there are valid frames for the decoder. It will split what is stored in the file into frames and return one for each call. It will not omit invalid data between valid frames so as to give the decoder the maximuminformation possible for decoding.此函数返回文件中存储的内容,但不验证解码器是否有有效的帧。它会将存储在文件中的内容拆分为帧,并为每个调用返回一个帧。它不会省略有效帧之间的无效数据,以便给解码器提供最大可能的解码信息On success, the returned packet is reference-counted (pkt->buf is set) andvalid indefinitely. The packet must be freed with av_packet_unref() whenit is no longer needed. For video, the packet contains exactly one frame.For audio, it contains an integer number of frames if each frame hasa known fixed size (e.g. PCM or ADPCM data). If the audio frames havea variable size (e.g. MPEG audio), then it contains one frame.pkt->pts, pkt->dts and pkt->duration are always set to correctvalues in AVStream.time_base units (and guessed if the format cannotprovide them). pkt->pts can be AV_NOPTS_VALUE if the video formathas B-frames, so it is better to rely on pkt->dts if you do notdecompress the payload.@return 0 if OK, < 0 on error or end of file. On error, pkt will be blank(as if it came from av_packet_alloc()).@note pkt will be initialized, so it may be uninitialized, but it must notcontain data that needs to be freed.*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);

av_packet_unref

/**Wipe the packet.擦除数据包Unreference the buffer referenced by the packet and reset theremaining packet fields to their default values.取消对数据包引用的缓冲区的引用,并将其余数据包字段重置为其默认值@param pkt The packet to be unreferenced.*/
void av_packet_unref(AVPacket *pkt);

av_rescale_q_rnd

/*** Rescale a 64-bit integer by 2 rational numbers with specified rounding.* 使用指定的舍入将64位整数重缩放2个有理数** The operation is mathematically equivalent to `a * bq / cq`.** @see av_rescale(), av_rescale_rnd(), av_rescale_q()*/
int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,enum AVRounding rnd) av_const;

av_rescale_q

/**Rescale a 64-bit integer by 2 rational numbers.将64位整数重缩放2个有理数The operation is mathematically equivalent to `a * bq / cq`.This function is equivalent to av_rescale_q_rnd() with #AV_ROUND_NEAR_INF.@see av_rescale(), av_rescale_rnd(), av_rescale_q_rnd()*/
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;

av_interleaved_write_frame

//将数据包写入输出媒体文件以确保正确的交织
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);

av_write_trailer

//将流片尾写入输出媒体文件并释放文件私有数据
int av_write_trailer(AVFormatContext *s);

参考资料

最简单的基于FFMPEG的封装格式转换器-无编解码(雷神)
最简单的基于FFMPEG的封装格式转换器-新
FFmpeg 源码之内存管理函数族

最简单的基于FFMPEG 4.2的封装格式转换器(无编解码MP4转FLV)相关推荐

  1. Android 最简单的基于FFmpeg的移动端例子:Android HelloWorld

    最简单的基于FFmpeg的移动端例子:Android HelloWorld 转载于:https://www.cnblogs.com/zhujiabin/p/6179199.html

  2. 最简单的基于FFmpeg的AVfilter的例子-修正版

    代码是参考雷神的博客的代码,不过由于ffmpeg版本不同,记录使用中遇到的问题. 1.调用avfilter_get_by_name("ffbuffersink")时在新版本的ffm ...

  3. 最简单的基于FFMPEG的封装格式转换器(无编解码)

    2019独角兽企业重金招聘Python工程师标准>>> 本文介绍一个基于FFMPEG的封装格式转换器.所谓的封装格式转换,就是在AVI,FLV,MKV,MP4这些格式之间转换(对应. ...

  4. 最简单的基于FFMPEG的Helloworld程序

    学习雷神的FFMPEG入门教程,本文基于命令行实现. 文件结构 G:\Coding\FFMpeg\Proj\Console>dir驱动器 G 中的卷没有标签.卷的序列号是 0FD5-0CC8G: ...

  5. 最简单的基于FFMPEG的推流器附件:收流器

    ===================================================== 最简单的基于FFmpeg的推流器系列文章列表: <最简单的基于FFmpeg的推流器(以 ...

  6. 最简单的基于FFmpeg的编码器-纯净版(不包含libavformat)

    ===================================================== 最简单的基于FFmpeg的视频编码器文章列表: 最简单的基于FFMPEG的视频编码器(YUV ...

  7. 最简单的基于FFMPEG的视频编码器(YUV编码为H.264)

    ===================================================== 最简单的基于FFmpeg的视频编码器文章列表: 最简单的基于FFMPEG的视频编码器(YUV ...

  8. 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  9. 最简单的基于FFmpeg的AVDevice例子(屏幕录制)

    ===================================================== 最简单的基于FFmpeg的AVDevice例子文章列表: 最简单的基于FFmpeg的AVDe ...

最新文章

  1. Java DataOutputStream writeInt()方法及示例
  2. 软件测试的目标及组织测试过程
  3. java分页插件使用_MyBatis-Plus之分页插件使用
  4. MatrixCursor 模拟数据库
  5. 关于 gzip, deflate, zlib, LZ77
  6. Linux系统无法载入nvidia-smi驱动
  7. java 关键字 val,java关键字final用法知识点
  8. Switch语句:空指针异常
  9. 《Java程序员,上班那点事儿》前言
  10. 你不可不知的《哈利波特》秘密(二)
  11. win7 搜索文件内容
  12. CANOpen协议详解(二):协议具体内容
  13. 幅相曲线渐近线_幅相曲线.ppt
  14. 变态级JAVA程序员面试32问(附答案)(转载)
  15. 淘宝订单API获取订单代码说明
  16. win 10新系统连接网络之后,跳过输入微软账户
  17. 非CS专业的人如何学才能够像CS专业一样
  18. 基于PCA方法的人脸识别(Python)
  19. 简单的建站流程来啦!
  20. nanoid 随即生成id

热门文章

  1. 2014_3_29_周赛 马腿上的最短路
  2. 评估区块链性能的要素
  3. 淘宝装修教程 淘宝美工教程 淘宝教程 淘宝美工职业之路
  4. linux查看数据库密码命令
  5. 机器学习算法: 朴素贝叶斯(Naive Bayes)
  6. linux下网络收音机,基于嵌入式Linux的流媒体网络收音机系统的设计与研究
  7. 全国计算机等级考试二级教程:c语言程序设计(2013年版),全国计算机等级考试二级教程:C语言程序设计(2013年版)...
  8. Mac系统下Python下载安装
  9. 西电计算机专业课资料汇总
  10. 基于JavaWeb的校友录同学录管理系统