一、FFMPEG的封装格式转换器(无编解码)

1.封装格式转换

所谓的封装格式转换,就是在AVI,FLV,MKV,MP4这些格式之间转换(对应.avi,.flv,.mkv,.mp4文件)。

需要注意的是,本程序并不进行视音频的编码和解码工作。而是直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。

本程序的工作原理如下图1所示:

由图可见,本程序并不进行视频和音频的编解码工作,因此本程序和普通的转码软件相比,有以下两个特点:

处理速度极快。视音频编解码算法十分复杂,占据了转码的绝大部分时间。因为不需要进行视音频的编码和解码,所以节约了大量的时间。

视音频质量无损。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。

2.基于FFmpeg的Remuxer的流程图

下面附上基于FFmpeg的Remuxer的流程图。图2中使用浅红色标出了关键的数据结构,浅蓝色标出了输出视频数据的函数。

可见成个程序包含了对两个文件的处理:读取输入文件(位于左边)和写入输出文件(位于右边)。中间使用了一个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)。

二、FFmpegRemuxer代码

基于FFmpeg的封装格式转换器,取了个名字称为FFmpegRemuxer

主要是FFmpegRemuxer.cpp文件,代码如下(基本上每一行都有注释):

  1. /*****************************************************************************
  2. * Copyright (C) 2017-2020 Hanson Yu All rights reserved.
  3. ------------------------------------------------------------------------------
  4. * File Module : FFmpegRemuxer.cpp
  5. * Description : FFmpegRemuxer Demo
  6.  
  7. 输出结果:
  8. Input #0, flv, from 'cuc_ieschool1.flv':
  9. Metadata:
  10. metadatacreator : iku
  11. hasKeyframes : true
  12. hasVideo : true
  13. hasAudio : true
  14. hasMetadata : true
  15. canSeekToEnd : false
  16. datasize : 932906
  17. videosize : 787866
  18. audiosize : 140052
  19. lasttimestamp : 34
  20. lastkeyframetimestamp: 30
  21. lastkeyframelocation: 886498
  22. encoder : Lavf55.19.104
  23. Duration: 00:00:34.20, start: 0.042000, bitrate: 394 kb/s
  24. Stream #0:0: Video: h264 (High), yuv420p, 512x288 [SAR 1:1 DAR 16:9], 15.17 fps, 15 tbr, 1k tbn, 30 tbc
  25. Stream #0:1: Audio: mp3, 44100 Hz, stereo, s16p, 128 kb/s
  26. Output #0, mp4, to 'cuc_ieschool1.mp4':
  27. Stream #0:0: Video: h264, yuv420p, 512x288 [SAR 1:1 DAR 16:9], q=2-31, 90k tbn, 30 tbc
  28. Stream #0:1: Audio: mp3, 44100 Hz, stereo, s16p, 128 kb/s
  29. Write 0 frames to output file
  30. Write 1 frames to output file
  31. Write 2 frames to output file
  32. Write 3 frames to output file
  33. .
  34. .
  35. .
  36.  
  37. * Created : 2017.09.21.
  38. * Author : Yu Weifeng
  39. * Function List :
  40. * Last Modified :
  41. * History :
  42. * Modify Date Version Author Modification
  43. * -----------------------------------------------
  44. * 2017/09/21 V1.0.0 Yu Weifeng Created
  45. ******************************************************************************/
  46. #include <stdio.h>
  47.  
  48. /*
  49. __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to allow C++ programs to use stdint.h
  50. macros specified in the C99 standard that aren't in the C++ standard. The macros, such as UINT8_MAX, INT64_MIN,
  51. and INT32_C() may be defined already in C++ applications in other ways. To allow the user to decide
  52. if they want the macros defined as C99 does, many implementations require that __STDC_LIMIT_MACROS
  53. and __STDC_CONSTANT_MACROS be defined before stdint.h is included.
  54.  
  55. This isn't part of the C++ standard, but it has been adopted by more than one implementation.
  56. */
  57. #define __STDC_CONSTANT_MACROS
  58.  
  59. #ifdef _WIN32//Windows
  60. extern "C"
  61. {
  62. #include "libavformat/avformat.h"
  63. };
  64. #else//Linux...
  65. #ifdef __cplusplus
  66. extern "C"
  67. {
  68. #endif
  69. #include <libavformat/avformat.h>
  70. #ifdef __cplusplus
  71. };
  72. #endif
  73. #endif
  74.  
  75. /*****************************************************************************
  76. -Fuction : main
  77. -Description : main
  78. -Input :
  79. -Output :
  80. -Return :
  81. * Modify Date Version Author Modification
  82. * -----------------------------------------------
  83. * 2017/09/21 V1.0.0 Yu Weifeng Created
  84. ******************************************************************************/
  85. int main(int argc, char* argv[])
  86. {
  87. AVOutputFormat * ptOutputFormat = NULL;//The output container format.Muxing only, must be set by the caller before avformat_write_header().
  88. AVFormatContext * ptInFormatContext = NULL;//输入文件的封装格式上下文,内部包含所有的视频信息
  89. AVFormatContext * ptOutFormatContext = NULL;//输出文件的封装格式上下文,内部包含所有的视频信息
  90. AVPacket tOutPacket ={};//存储一帧压缩编码数据给输出文件
  91. const char * strInFileName=NULL, * strOutFileName = NULL;//输入文件名和输出文件名
  92. int iRet, i;
  93. int iFrameCount = ;//输出的帧个数
  94. AVStream * ptInStream=NULL,* ptOutStream=NULL;//输入音视频流和输出音视频流
  95.  
  96. if(argc!=)//argc包括argv[0]也就是程序名称
  97. {
  98. printf("Usage:%s InputFileURL OutputFileURL\r\n",argv[]);
  99. printf("For example:\r\n");
  100. printf("%s InputFile.flv OutputFile.mp4\r\n",argv[]);
  101. return -;
  102. }
  103. strInFileName = argv[];//Input file URL
  104. strOutFileName = argv[];//Output file URL
  105.  
  106. av_register_all();//注册FFmpeg所有组件
  107.  
  108. /*------------Input------------*/
  109. if ((iRet = avformat_open_input(&ptInFormatContext, strInFileName, , )) < )
  110. {//打开输入视频文件
  111. printf("Could not open input file\r\n");
  112. }
  113. else
  114. {
  115. if ((iRet = avformat_find_stream_info(ptInFormatContext, )) < )
  116. {//获取视频文件信息
  117. printf("Failed to find input stream information\r\n");
  118. }
  119. else
  120. {
  121. av_dump_format(ptInFormatContext, , strInFileName, );//手工调试的函数,内部是log,输出相关的格式信息到log里面
  122.  
  123. /*------------Output------------*/
  124.  
  125. /*初始化一个用于输出的AVFormatContext结构体
  126. *ctx:函数调用成功之后创建的AVFormatContext结构体。
  127. *oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,
  128. 可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。
  129. PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。
  130. *format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。
  131. *filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。
  132. 函数执行成功的话,其返回值大于等于0
  133. */
  134. avformat_alloc_output_context2(&ptOutFormatContext, NULL, NULL, strOutFileName);
  135. if (!ptOutFormatContext)
  136. {
  137. printf("Could not create output context\r\n");
  138. iRet = AVERROR_UNKNOWN;
  139. }
  140. else
  141. {
  142. ptOutputFormat = ptOutFormatContext->oformat;
  143. for (i = ; i < ptInFormatContext->nb_streams; i++)
  144. {
  145. //Create output AVStream according to input AVStream
  146. ptInStream = ptInFormatContext->streams[i];
  147. ptOutStream = avformat_new_stream(ptOutFormatContext, ptInStream->codec->codec);//给ptOutFormatContext中的流数组streams中的
  148. if (!ptOutStream) //一条流(数组中的元素)分配空间,也正是由于这里分配了空间,后续操作直接拷贝编码数据(pkt)就可以了。
  149. {
  150. printf("Failed allocating output stream\r\\n");
  151. iRet = AVERROR_UNKNOWN;
  152. break;
  153. }
  154. else
  155. {
  156. if (avcodec_copy_context(ptOutStream->codec, ptInStream->codec) < ) //Copy the settings of AVCodecContext
  157. {
  158. printf("Failed to copy context from input to output stream codec context\r\n");
  159. iRet = AVERROR_UNKNOWN;
  160. break;
  161. }
  162. else
  163. {
  164. ptOutStream->codec->codec_tag = ;
  165. if (ptOutFormatContext->oformat->flags & AVFMT_GLOBALHEADER)
  166. ptOutStream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
  167.  
  168. }
  169. }
  170. }
  171. if(AVERROR_UNKNOWN == iRet)
  172. {
  173. }
  174. else
  175. {
  176. av_dump_format(ptOutFormatContext, , strOutFileName, );//Output information------------------
  177. //Open output file
  178. if (!(ptOutputFormat->flags & AVFMT_NOFILE))
  179. { /*打开FFmpeg的输入输出文件,使后续读写操作可以执行
  180. *s:函数调用成功之后创建的AVIOContext结构体。
  181. *url:输入输出协议的地址(文件也是一种“广义”的协议,对于文件来说就是文件的路径)。
  182. *flags:打开地址的方式。可以选择只读,只写,或者读写。取值如下。
  183. AVIO_FLAG_READ:只读。AVIO_FLAG_WRITE:只写。AVIO_FLAG_READ_WRITE:读写。*/
  184. iRet = avio_open(&ptOutFormatContext->pb, strOutFileName, AVIO_FLAG_WRITE);
  185. if (iRet < )
  186. {
  187. printf("Could not open output file %s\r\n", strOutFileName);
  188. }
  189. else
  190. {
  191. //Write file header
  192. if (avformat_write_header(ptOutFormatContext, NULL) < ) //avformat_write_header()中最关键的地方就是调用了AVOutputFormat的write_header()
  193. {//不同的AVOutputFormat有不同的write_header()的实现方法
  194. printf("Error occurred when opening output file\r\n");
  195. }
  196. else
  197. {
  198. while ()
  199. {
  200. //Get an AVPacket
  201. iRet = av_read_frame(ptInFormatContext, &tOutPacket);//从输入文件读取一帧压缩数据
  202. if (iRet < )
  203. break;
  204.  
  205. ptInStream = ptInFormatContext->streams[tOutPacket.stream_index];
  206. ptOutStream = ptOutFormatContext->streams[tOutPacket.stream_index];
  207. //Convert PTS/DTS
  208. tOutPacket.pts = av_rescale_q_rnd(tOutPacket.pts, ptInStream->time_base, ptOutStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  209. tOutPacket.dts = av_rescale_q_rnd(tOutPacket.dts, ptInStream->time_base, ptOutStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  210. tOutPacket.duration = av_rescale_q(tOutPacket.duration, ptInStream->time_base, ptOutStream->time_base);
  211. tOutPacket.pos = -;
  212. //Write
  213. /*av_interleaved_write_frame包括interleave_packet()以及write_packet(),将还未输出的AVPacket输出出来
  214. *write_packet()函数最关键的地方就是调用了AVOutputFormat中写入数据的方法。write_packet()实际上是一个函数指针,
  215. 指向特定的AVOutputFormat中的实现函数*/
  216. if (av_interleaved_write_frame(ptOutFormatContext, &tOutPacket) < )
  217. {
  218. printf("Error muxing packet\r\n");
  219. break;
  220. }
  221. printf("Write %8d frames to output file\r\n", iFrameCount);
  222. av_free_packet(&tOutPacket);//释放空间
  223. iFrameCount++;
  224. }
  225. //Write file trailer//av_write_trailer()中最关键的地方就是调用了AVOutputFormat的write_trailer()
  226. av_write_trailer(ptOutFormatContext);//不同的AVOutputFormat有不同的write_trailer()的实现方法
  227. }
  228. if (ptOutFormatContext && !(ptOutputFormat->flags & AVFMT_NOFILE))
  229. avio_close(ptOutFormatContext->pb);//该函数用于关闭一个AVFormatContext->pb,一般情况下是和avio_open()成对使用的。
  230. }
  231. }
  232. }
  233. avformat_free_context(ptOutFormatContext);//释放空间
  234. }
  235. }
  236. avformat_close_input(&ptInFormatContext);//该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。
  237. }
  238. return ;
  239. }

FFmpegRemuxer.cpp

具体代码见github:

https://github.com/fengweiyu/FFmpegFormat/FFmpegRemuxer

三、FFmpeg的封装格式处理:视音频复用器(muxer)

1.封装格式处理

视音频复用器(Muxer)即是将视频压缩数据(例如H.264)和音频压缩数据(例如AAC)合并到一个封装格式数据(例如MKV)中去。

如图3所示。在这个过程中并不涉及到编码和解码。

2.基于FFmpeg的muxer的流程图

程序的流程如下图4所示。从流程图中可以看出,一共初始化了3个AVFormatContext,其中2个用于输入,1个用于输出。3个AVFormatContext初始化之后,通过avcodec_copy_context()函数可以将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体。

然后分别调用视频输入流和音频输入流的av_read_frame(),从视频输入流中取出视频的AVPacket,音频输入流中取出音频的AVPacket,分别将取出的AVPacket写入到输出文件中即可。

其间用到了一个不太常见的函数av_compare_ts(),是比较时间戳用的。通过该函数可以决定该写入视频还是音频。

本文介绍的视音频复用器,输入的视频不一定是H.264裸流文件,音频也不一定是纯音频文件。可以选择两个封装过的视音频文件作为输入。程序会从视频输入文件中“挑”出视频流,音频输入文件中“挑”出音频流,再将“挑选”出来的视音频流复用起来。

PS1:对于某些封装格式(例如MP4/FLV/MKV等)中的H.264,需要用到名称为“h264_mp4toannexb”的bitstream filter。

PS2:对于某些封装格式(例如MP4/FLV/MKV等)中的AAC,需要用到名称为“aac_adtstoasc”的bitstream filter。

简单介绍一下流程中各个重要函数的意义:

avformat_open_input():打开输入文件。

avcodec_copy_context():赋值AVCodecContext的参数。

avformat_alloc_output_context2():初始化输出文件。

avio_open():打开输出文件。

avformat_write_header():写入文件头。

av_compare_ts():比较时间戳,决定写入视频还是写入音频。这个函数相对要少见一些。

av_read_frame():从输入文件读取一个AVPacket。

av_interleaved_write_frame():写入一个AVPacket到输出文件。

av_write_trailer():写入文件尾。

3.优化为可以从内存中读取音视频数据

打开文件的函数是avformat_open_input(),直接将文件路径或者流媒体URL的字符串传递给该函数就可以了。

但其是否支持从内存中读取数据呢?

分析ffmpeg的源代码,发现其竟然是可以从内存中读取数据的,代码很简单,如下所示:

ptInFormatContext = avformat_alloc_context();

pbIoBuf = (unsigned char *)av_malloc(IO_BUFFER_SIZE);

ptAVIO = avio_alloc_context(pbIoBuf, IO_BUFFER_SIZE, 0, NULL, FillIoBuffer, NULL, NULL);

ptInFormatContext->pb = ptAVIO;

ptInputFormat = av_find_input_format("h264");//得到ptInputFormat以便后面打开使用

if ((iRet = avformat_open_input(&ptInFormatContext, "", ptInputFormat, NULL)) < 0)

{

printf("Could not open input file\r\n");

}

else

{

}

关键要在avformat_open_input()之前初始化一个AVIOContext,而且将原本的AVFormatContext的指针pb(AVIOContext类型)指向这个自行初始化AVIOContext。

当自行指定了AVIOContext之后,avformat_open_input()里面的URL参数就不起作用了。示例代码开辟了一块空间iobuffer作为AVIOContext的缓存。

FillIoBuffer则是将数据读取至iobuffer的回调函数。FillIoBuffer()形式(参数,返回值)是固定的,是一个回调函数,如下所示(只是个例子,具体怎么读取数据可以自行设计)。

示例中回调函数将文件中的内容通过fread()读入内存。

int FillIoBuffer(void *opaque, unsigned char *o_pbBuf, int i_iMaxSize)

{

int iRet=-1;

if (!feof(g_fileH264))

{

iRet = fread(o_pbBuf, 1, i_iMaxSize, g_fileH264);

}

else

{

}

return iRet;

}

整体结构大致如下:

FILE *fp_open;

int fill_iobuffer(void *opaque, uint8_t *buf, int buf_size){

...

}

int main(){

...

fp_open=fopen("test.h264","rb+");

AVFormatContext *ic = NULL;

ic = avformat_alloc_context();

unsigned char * iobuffer=(unsigned char *)av_malloc(32768);

AVIOContext *avio =avio_alloc_context(iobuffer, 32768,0,NULL,fill_iobuffer,NULL,NULL);

ic->pb=avio;

err = avformat_open_input(&ic, "nothing", NULL, NULL);

...//解码

}

4.将音视频数据输出到内存

同时再说明一下,和从内存中读取数据类似,ffmpeg也可以将处理后的数据输出到内存。

回调函数如下示例,可以将输出到内存的数据写入到文件中。

//写文件的回调函数

int write_buffer(void *opaque, uint8_t *buf, int buf_size){

if(!feof(fp_write)){

int true_size=fwrite(buf,1,buf_size,fp_write);

return true_size;

}else{

return -1;

}

}

主函数如下所示:

FILE *fp_write;

int write_buffer(void *opaque, uint8_t *buf, int buf_size){

...

}

main(){

...

fp_write=fopen("src01.h264","wb+"); //输出文件

...

AVFormatContext* ofmt_ctx=NULL;

avformat_alloc_output_context2(&ofmt_ctx, NULL, "h264", NULL);

unsigned char* outbuffer=(unsigned char*)av_malloc(32768);

AVIOContext *avio_out =avio_alloc_context(outbuffer, 32768,0,NULL,NULL,write_buffer,NULL);

ofmt_ctx->pb=avio_out;

ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO;

...

}

从上述可以很明显的看到,知道把写回调函数放到avio_alloc_context函数对应的位置就可以了。

四、FFmpegMuxer代码

基于FFmpeg的视音频复用器,取了个名字称为FFmpegMuxer

主要是FFmpegMuxer.cpp文件,代码如下(基本上每一行都有注释):

  1. /*****************************************************************************
  2. * Copyright (C) 2017-2020 Hanson Yu All rights reserved.
  3. ------------------------------------------------------------------------------
  4. * File Module : FFmpegMuxer.cpp
  5. * Description : FFmpegMuxer Demo
  6.  
  7. *先将H.264文件读入内存,
  8. *再输出封装格式文件。
  9.  
  10. 输出结果:
  11. book@book-desktop:/work/project/FFmpegMuxer$ make clean;make
  12. rm FFmpegMuxer
  13. g++ FFmpegMuxer.cpp -I ./include -rdynamic ./lib/libavformat.so.57 ./lib/libavcodec.so.57 ./lib/libavutil.so.55 ./lib/libswresample.so.2 -o FFmpegMuxer
  14. book@book-desktop:/work/project/FFmpegMuxer$ export LD_LIBRARY_PATH=./lib
  15. book@book-desktop:/work/project/FFmpegMuxer$ ./FFmpegMuxer sintel.h264 sintel.mp4
  16. Input #0, h264, from 'sintel.h264':
  17. Duration: N/A, bitrate: N/A
  18. Stream #0:0: Video: h264 (High), yuv420p(progressive), 640x360, 25 fps, 25 tbr, 1200k tbn, 50 tbc
  19. Output #0, mp4, to 'sintel.mp4':
  20. Stream #0:0: Unknown: none
  21. [mp4 @ 0x9352d80] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
  22. [mp4 @ 0x9352d80] Using AVStream.codec to pass codec parameters to muxers is deprecated, use AVStream.codecpar instead.
  23. Write iFrameIndex:1,stream_index:0,num:25,den:1
  24. Write iFrameIndex:2,stream_index:0,num:25,den:1
  25. Write iFrameIndex:3,stream_index:0,num:25,den:1
  26. .
  27. .
  28. .
  29.  
  30. * Created : 2017.09.21.
  31. * Author : Yu Weifeng
  32. * Function List :
  33. * Last Modified :
  34. * History :
  35. * Modify Date Version Author Modification
  36. * -----------------------------------------------
  37. * 2017/09/21 V1.0.0 Yu Weifeng Created
  38. ******************************************************************************/
  39. #include <stdio.h>
  40.  
  41. /*
  42. __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to allow C++ programs to use stdint.h
  43. macros specified in the C99 standard that aren't in the C++ standard. The macros, such as UINT8_MAX, INT64_MIN,
  44. and INT32_C() may be defined already in C++ applications in other ways. To allow the user to decide
  45. if they want the macros defined as C99 does, many implementations require that __STDC_LIMIT_MACROS
  46. and __STDC_CONSTANT_MACROS be defined before stdint.h is included.
  47.  
  48. This isn't part of the C++ standard, but it has been adopted by more than one implementation.
  49. */
  50. #define __STDC_CONSTANT_MACROS
  51.  
  52. #ifdef _WIN32//Windows
  53. extern "C"
  54. {
  55. #include "libavformat/avformat.h"
  56. };
  57. #else//Linux...
  58. #ifdef __cplusplus
  59. extern "C"
  60. {
  61. #endif
  62. #include <libavformat/avformat.h>
  63. #ifdef __cplusplus
  64. };
  65. #endif
  66. #endif
  67.  
  68. #define IO_BUFFER_SIZE 32768 //缓存32k
  69.  
  70. static FILE * g_fileH264=NULL;
  71.  
  72. /*****************************************************************************
  73. -Fuction : FillIoBuffer
  74. -Description : FillIoBuffer
  75.  
  76. *在avformat_open_input()中会首次调用该回调函数,
  77. *第二次一直到最后一次都是在avformat_find_stream_info()中循环调用,
  78. *文件中的数据每次IO_BUFFER_SIZE字节读入到内存中,
  79. *经过ffmpeg处理,所有数据被有序地逐帧存储到AVPacketList中。
  80. *以上是缓存设为32KB的情况,缓存大小设置不同,调用机制也有所不同。
  81.  
  82. -Input :
  83. -Output :
  84. -Return : 返回读取的长度
  85. * Modify Date Version Author Modification
  86. * -----------------------------------------------
  87. * 2017/09/21 V1.0.0 Yu Weifeng Created
  88. ******************************************************************************/
  89. int FillIoBuffer(void *opaque, unsigned char *o_pbBuf, int i_iMaxSize)
  90. {
  91. int iRet=-;
  92. if (!feof(g_fileH264))
  93. {
  94. iRet = fread(o_pbBuf, , i_iMaxSize, g_fileH264);
  95. }
  96. else
  97. {
  98. }
  99. return iRet;
  100. }
  101.  
  102. /*****************************************************************************
  103. -Fuction : main
  104. -Description : main
  105. 关键要在avformat_open_input()之前初始化一个AVIOContext,
  106. 而且将原本的AVFormatContext的指针pb(AVIOContext类型)指向这个自行初始化AVIOContext
  107. -Input :
  108. -Output :
  109. -Return :
  110. * Modify Date Version Author Modification
  111. * -----------------------------------------------
  112. * 2017/09/21 V1.0.0 Yu Weifeng Created
  113. ******************************************************************************/
  114. int main(int argc, char* argv[])
  115. {
  116. AVInputFormat * ptInputFormat = NULL;//The output container format.Muxing only, must be set by the caller before avformat_write_header().
  117. AVOutputFormat * ptOutputFormat = NULL;//The output container format.Muxing only, must be set by the caller before avformat_write_header().
  118. AVFormatContext * ptInFormatContext = NULL;//输入文件的封装格式上下文,内部包含所有的视频信息
  119. AVFormatContext * ptOutFormatContext = NULL;//输出文件的封装格式上下文,内部包含所有的视频信息
  120. AVPacket tOutPacket ={};//存储一帧压缩编码数据给输出文件
  121. const char * strInVideoFileName=NULL, * strOutFileName = NULL;//输入文件名和输出文件名
  122. int iRet, i;
  123. int iVideoStreamIndex = -;//视频流应该处在的位置
  124. int iFrameIndex = ;
  125. long long llCurrentPts = ;
  126. int iOutVideoStreamIndex = -; //输出流中的视频流所在的位置
  127. AVStream * ptInStream=NULL,* ptOutStream=NULL;//输入音视频流和输出音视频流
  128. unsigned char * pbIoBuf=NULL;//io数据缓冲区
  129. AVIOContext * ptAVIO=NULL;//AVIOContext管理输入输出数据的结构体
  130.  
  131. if(argc!=)//argc包括argv[0]也就是程序名称
  132. {
  133. printf("Usage:%s InputVideoFileURL OutputFileURL\r\n",argv[]);
  134. printf("For example:\r\n");
  135. printf("%s InputFile.h264 OutputFile.mp4\r\n",argv[]);
  136. return -;
  137. }
  138. strInVideoFileName = argv[];//Input file URL
  139. strOutFileName = argv[];//Output file URL
  140.  
  141. av_register_all();//注册FFmpeg所有组件
  142.  
  143. /*------------Input:填充ptInFormatContext------------*/
  144. g_fileH264 = fopen(strInVideoFileName, "rb+");
  145. ptInFormatContext = avformat_alloc_context();
  146. pbIoBuf = (unsigned char *)av_malloc(IO_BUFFER_SIZE);
  147. //FillIoBuffer则是将数据读取至pbIoBuf的回调函数。FillIoBuffer()形式(参数,返回值)是固定的,是一个回调函数,
  148. ptAVIO = avio_alloc_context(pbIoBuf, IO_BUFFER_SIZE, , NULL, FillIoBuffer, NULL, NULL); //当系统需要数据的时候,会自动调用该回调函数以获取数据
  149. ptInFormatContext->pb = ptAVIO; //当自行指定了AVIOContext之后,avformat_open_input()里面的URL参数就不起作用了
  150.  
  151. ptInputFormat = av_find_input_format("h264");//得到ptInputFormat以便后面打开使用
  152. //ps:函数调用成功之后处理过的AVFormatContext结构体;file:打开的视音频流的文件路径或者流媒体URL;fmt:强制指定AVFormatContext中AVInputFormat的,为NULL,FFmpeg通过文件路径或者流媒体URL自动检测;dictionay:附加的一些选项,一般情况下可以设置为NULL
  153. //内部主要调用两个函数:init_input():绝大部分初始化工作都是在这里做的。s->iformat->read_header():读取多媒体数据文件头,根据视音频流创建相应的AVStream
  154. if ((iRet = avformat_open_input(&ptInFormatContext, "", ptInputFormat, NULL)) < ) //其中的init_input()如果指定了fmt(第三个参数,比如当前就有指定)就直接返回,如果没有指定就调用av_probe_input_buffer2()推测AVInputFormat
  155. {//打开输入视频源//自定义了回调函数FillIoBuffer()。在使用avformat_open_input()打开媒体数据的时候,就可以不指定文件的URL了,即其第2个参数为NULL(因为数据不是靠文件读取,而是由FillIoBuffer()提供)
  156. printf("Could not open input file\r\n");
  157. }
  158. else
  159. {
  160. if ((iRet = avformat_find_stream_info(ptInFormatContext, )) < )
  161. {//获取视频文件信息
  162. printf("Failed to find input stream information\r\n");
  163. }
  164. else
  165. {
  166. av_dump_format(ptInFormatContext, , strInVideoFileName, );//手工调试的函数,内部是log,输出相关的格式信息到log里面
  167.  
  168. /*------------Output------------*/
  169.  
  170. /*初始化一个用于输出的AVFormatContext结构体
  171. *ctx:函数调用成功之后创建的AVFormatContext结构体。
  172. *oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,
  173. 可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。
  174. PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。
  175. *format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。
  176. *filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。
  177. 函数执行成功的话,其返回值大于等于0
  178. */
  179. avformat_alloc_output_context2(&ptOutFormatContext, NULL, NULL, strOutFileName);
  180. if (!ptOutFormatContext)
  181. {
  182. printf("Could not create output context\r\n");
  183. iRet = AVERROR_UNKNOWN;
  184. }
  185. else
  186. {
  187. ptOutputFormat = ptOutFormatContext->oformat;
  188. //for (i = 0; i < ptInFormatContext->nb_streams; i++)
  189. {
  190. //Create output AVStream according to input AVStream
  191. ptInStream = ptInFormatContext->streams[];//0 video
  192. ptOutStream = avformat_new_stream(ptOutFormatContext, ptInStream->codec->codec);//给ptOutFormatContext中的流数组streams中的
  193. if (!ptOutStream) //一条流(数组中的元素)分配空间,也正是由于这里分配了空间,后续操作直接拷贝编码数据(pkt)就可以了。
  194. {
  195. printf("Failed allocating output stream\r\\n");
  196. iRet = AVERROR_UNKNOWN;
  197. //break;
  198. }
  199. else
  200. {
  201. iVideoStreamIndex=;
  202. iOutVideoStreamIndex = ptOutStream->index; //保存视频流所在数组的位置
  203. if (avcodec_copy_context(ptOutStream->codec, ptInStream->codec) < ) //Copy the settings of AVCodecContext
  204. {//avcodec_copy_context()函数可以将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体
  205. printf("Failed to copy context from input to output stream codec context\r\n");
  206. iRet = AVERROR_UNKNOWN;
  207. //break;
  208. }
  209. else
  210. {
  211. ptOutStream->codec->codec_tag = ;
  212. if (ptOutFormatContext->oformat->flags & AVFMT_GLOBALHEADER)
  213. ptOutStream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
  214.  
  215. }
  216. }
  217. }
  218. if(AVERROR_UNKNOWN == iRet)
  219. {
  220. }
  221. else
  222. {
  223. av_dump_format(ptOutFormatContext, , strOutFileName, );//Output information------------------
  224. //Open output file
  225. if (!(ptOutputFormat->flags & AVFMT_NOFILE))
  226. { /*打开FFmpeg的输入输出文件,使后续读写操作可以执行
  227. *s:函数调用成功之后创建的AVIOContext结构体。
  228. *url:输入输出协议的地址(文件也是一种“广义”的协议,对于文件来说就是文件的路径)。
  229. *flags:打开地址的方式。可以选择只读,只写,或者读写。取值如下。
  230. AVIO_FLAG_READ:只读。AVIO_FLAG_WRITE:只写。AVIO_FLAG_READ_WRITE:读写。*/
  231. iRet = avio_open(&ptOutFormatContext->pb, strOutFileName, AVIO_FLAG_WRITE);
  232. if (iRet < )
  233. {
  234. printf("Could not open output file %s\r\n", strOutFileName);
  235. }
  236. else
  237. {
  238. //Write file header
  239. if (avformat_write_header(ptOutFormatContext, NULL) < ) //avformat_write_header()中最关键的地方就是调用了AVOutputFormat的write_header()
  240. {//不同的AVOutputFormat有不同的write_header()的实现方法
  241. printf("Error occurred when opening output file\r\n");
  242. }
  243. else
  244. {
  245. while ()
  246. {
  247. int iStreamIndex = -;//用于标识当前是哪个流
  248. iStreamIndex = iOutVideoStreamIndex;
  249. //Get an AVPacket//从视频输入流中取出视频的AVPacket
  250. iRet = av_read_frame(ptInFormatContext, &tOutPacket);//从输入文件读取一帧压缩数据
  251. if (iRet < )
  252. break;
  253. else
  254. {
  255. do{
  256. ptInStream = ptInFormatContext->streams[tOutPacket.stream_index];
  257. ptOutStream = ptOutFormatContext->streams[iStreamIndex];
  258. if (tOutPacket.stream_index == iVideoStreamIndex)
  259. { //H.264裸流没有PTS,因此必须手动写入PTS,应该放在av_read_frame()之后
  260. //FIX:No PTS (Example: Raw H.264)
  261. //Simple Write PTS
  262. if (tOutPacket.pts == AV_NOPTS_VALUE)
  263. {
  264. //Write PTS
  265. AVRational time_base1 = ptInStream->time_base;
  266. //Duration between 2 frames (μs) 。假设25帧,两帧间隔40ms //AV_TIME_BASE表示1s,所以用它的单位为us,也就是ffmpeg中都是us
  267. //int64_t calc_duration = AV_TIME_BASE*1/25;//或40*1000;//(double)AV_TIME_BASE / av_q2d(ptInStream->r_frame_rate);//ptInStream->r_frame_rate.den等于0所以注释掉
  268. //帧率也可以从h264的流中获取,前面dump就有输出,但是不知道为何同样的变量前面r_frame_rate打印正常,这里使用的时候却不正常了,所以这个间隔时间只能使用avg_frame_rate或者根据假设帧率来写
  269. int64_t calc_duration =(double)AV_TIME_BASE / av_q2d(ptInStream->avg_frame_rate);
  270. //Parameters pts(显示时间戳)*pts单位(时间基*时间基单位)=真实显示的时间(所谓帧的显示时间都是相对第一帧来的)
  271. tOutPacket.pts = (double)(iFrameIndex*calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE);//AV_TIME_BASE为1s,所以其单位为us
  272. tOutPacket.dts = tOutPacket.pts;
  273. tOutPacket.duration = (double)calc_duration / (double)(av_q2d(time_base1)*AV_TIME_BASE);
  274. iFrameIndex++;
  275. printf("Write iFrameIndex:%d,stream_index:%d,num:%d,den:%d\r\n",iFrameIndex, tOutPacket.stream_index,ptInStream->avg_frame_rate.num,ptInStream->avg_frame_rate.den);
  276. }
  277. llCurrentPts = tOutPacket.pts;
  278. break;
  279. }
  280. } while (av_read_frame(ptInFormatContext, &tOutPacket) >= );
  281. }
  282.  
  283. //Convert PTS/DTS
  284. tOutPacket.pts = av_rescale_q_rnd(tOutPacket.pts, ptInStream->time_base, ptOutStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  285. tOutPacket.dts = av_rescale_q_rnd(tOutPacket.dts, ptInStream->time_base, ptOutStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  286. tOutPacket.duration = av_rescale_q(tOutPacket.duration, ptInStream->time_base, ptOutStream->time_base);
  287. tOutPacket.pos = -;
  288. tOutPacket.stream_index = iStreamIndex;
  289. //printf("Write 1 Packet. size:%5d\tpts:%lld\n", tOutPacket.size, tOutPacket.pts);
  290. //Write
  291. /*av_interleaved_write_frame包括interleave_packet()以及write_packet(),将还未输出的AVPacket输出出来
  292. *write_packet()函数最关键的地方就是调用了AVOutputFormat中写入数据的方法。write_packet()实际上是一个函数指针,
  293. 指向特定的AVOutputFormat中的实现函数*/
  294. if (av_interleaved_write_frame(ptOutFormatContext, &tOutPacket) < )
  295. {
  296. printf("Error muxing packet\r\n");
  297. break;
  298. }
  299. av_free_packet(&tOutPacket);//释放空间
  300. }
  301. //Write file trailer//av_write_trailer()中最关键的地方就是调用了AVOutputFormat的write_trailer()
  302. av_write_trailer(ptOutFormatContext);//不同的AVOutputFormat有不同的write_trailer()的实现方法
  303. }
  304. if (ptOutFormatContext && !(ptOutputFormat->flags & AVFMT_NOFILE))
  305. avio_close(ptOutFormatContext->pb);//该函数用于关闭一个AVFormatContext->pb,一般情况下是和avio_open()成对使用的。
  306. }
  307. }
  308. }
  309. avformat_free_context(ptOutFormatContext);//释放空间
  310. }
  311. }
  312. avformat_close_input(&ptInFormatContext);//该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。
  313. }
  314. if(NULL!=g_fileH264)
  315. fclose(g_fileH264);
  316. return ;
  317. }

FFmpegMuxer.cpp

具体代码见github:

https://github.com/fengweiyu/FFmpegFormat/FFmpegMuxer

五、参考原文:

https://blog.csdn.net/leixiaohua1020/article/details/25422685

https://blog.csdn.net/leixiaohua1020/article/details/39802913

https://blog.csdn.net/leixiaohua1020/article/details/12980423

https://blog.csdn.net/leixiaohua1020/article/details/39759163

音视频处理之FFmpeg封装格式20180510的更多相关文章

  1. FFmpeg封装格式处理3-复用例程

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10506653.html FFmpeg封装格式处理相关内容分为如下几篇文章: [1]. F ...

  2. FFmpeg封装格式处理

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10506636.html FFmpeg封装格式处理相关内容分为如下几篇文章: [1]. F ...

  3. FFmpeg封装格式处理2-解复用例程

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10506642.html FFmpeg封装格式处理相关内容分为如下几篇文章: [1]. F ...

  4. FFmpeg封装格式处理4-转封装例程

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10506662.html FFmpeg封装格式处理相关内容分为如下几篇文章: [1]. F ...

  5. 音视频处理之FFmpeg+SDL+MFC视频播放器20180411

    一.FFmpeg+SDL+MFC视频播放器 1.MFC知识 1).创建MFC工程的方法 打开VC++ 文件->新建->项目->MFC应用程序 应用程序类型->基于对话框 取消勾 ...

  6. 音视频处理之FFmpeg+SDL视频播放器20180409

    一.FFmpeg视频解码器 1.视频解码知识 1).纯净的视频解码流程 压缩编码数据->像素数据. 例如解码H.264,就是“H.264码流->YUV”. 2).一般的视频解码流程 视频码 ...

  7. 音视频】5.ffmpeg命令分类与使用

    GT其实平时也有一些处理音视频的个人或者亲人需求,熟练使用ffmpeg之后也不要借助图示化软件,一个命令基本可以搞定 G: 熟练使用ffmpeg命令!T :不要死记硬背,看一遍,自己找下规律,敲一遍, ...

  8. Android 音视频深入 十一 FFmpeg和AudioTrack播放声音(附源码下载)

    项目地址,求starhttps://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E6%92%AD%E6%94%BE%E ...

  9. Android 音视频深入 九 FFmpeg解码视频生成yuv文件(附源码下载)

    项目地址,求star https://github.com/979451341/Audio-and-video-learning-materials/tree/master/FFmpeg(MP4%E8 ...

随机推荐

  1. css 剩余宽度完全填充

    从网上转的. <html> <head> <meta http-equiv="Content-Type" content="text/htm ...

  2. [linux] LVM磁盘管理(针对xfs和ext4不同文件系统)

    简单来说就是:PV:是物理的磁盘分区VG:LVM中的物理的磁盘分区,也就是PV,必须加入VG,可以将VG理解为一个仓库或者是几个大的硬盘LV:也就是从VG中划分的逻辑分区如下图所示PV.VG.LV三者 ...

  3. 第三周vim入门学习1

    一.vim模式介绍 1.概念:以下介绍内容来自维基百科Vim 从vi演生出来的Vim具有多种模式,这种独特的设计容易使初学者产生混淆.几乎所有的编辑器都会有插入和执行命令两种模式,并且大多数的编辑器使 ...

  4. delphi 图像处理 图像左旋右旋

    procedure TDR_QM_ZP_Form.btn_ZXClick(Sender: TObject); //图像左旋 begin screen.Cursor := crhourglass; my ...

  5. sprint站立会议

    索引卡: 工作认领:                                                                                       时间 ...

  6. 《Spring1之第十次站立会议》

    <第十次站立会议> 昨天:试着把用C#写的代码转换为java语言. 今天:已基本转换为java语言了,也能够实现视频聊天这个功能了. 遇到的问题:在进行视频通话时没有考虑到声音优化功能,实 ...

  7. Hibernate主键注解

    http://www.cnblogs.com/hongten/archive/2011/07/20/2111773.html 版权声明:本文为博主原创文章,未经博主允许不得转载.

  8. Codeforces Round #304 (Div. 2) E. Soldier and Traveling 最大流

    题目链接: http://codeforces.com/problemset/problem/546/E E. Soldier and Traveling time limit per test1 s ...

  9. 使用百度地图api可视化聚类结果

    1.写在前面 上接YFCC 100M数据集分析笔记,在对聚类出的照片GEO集聚类后,为了方便检测聚类结果,我们显示直接采用了 python 的 matplotlib 库以经纬度为坐标画出聚类结果,但发 ...

  10. Scrum 5.0

    5.0--------------------------------------------------- 1.团队成员完成自己认领的任务. 2.燃尽图:理解.设计并画出本次Sprint的燃尽图的理 ...