当前位置:   article > 正文

ffmpeg音视频裁剪_ffmpeg剪辑音频

ffmpeg剪辑音频

音视频裁剪,通常会依据时间轴为基准,从某个起始点到终止点的音视频截取出来,当然音视频文件中存在多路流,所对每一组流进行裁剪 

 基础概念:

编码帧的分类:

I帧(Intra coded frames):  关键帧,采用帧内压缩技术,所占数据的信息量比较大,I帧不需要参考其他画面而生成,解码时仅靠自己就重构完整图像;

P 帧(forward Predicted frames):  向前参考帧,根据本帧与相邻的前一帧(l帧或P帧)的不同点来压缩本帧数据,同时利用了空间和时间上的相关性。压缩时,只参考前面已经处理的帧(I帧或P帧),采用帧间压缩技术。它占I帧的一半大小

B 帧(Bidirectional predicted frames):  双向参考帧,B 帧图像采用双向时间预测,可以大大提高压缩倍数。压缩时,既参考前面已经处理的帧,也参考后面的帧,帧间压缩技术。它占I帧四分之一大小。

        I帧图像是周期性出现在图像序列中的,出现频率可由编码器选择;I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);I帧是帧组GOP(Group of Pictures)的基础帧(第一帧),且每组只有一个I帧。

        对于一个视频文件,帧的显示顺序:IBBP,但是帧的存储方式可能是:IPBB。现在我们需要在显示B帧之前知道P帧中的信息,这时就需要一个解码时间戳(dts(Decoding Time Stamp))和一个显示时间戳(pts(Presentation Time Stamp))。解码时间戳告诉我们什么时候需要解码,显示时间戳告诉我们什么时间需要显示。通常pts和dts只有在流中有B帧的时候才不同。

        FFmpeg中用AVPacket结构体来描述解码前、后的压缩包,用AVFrame结构体来描述解码后、前的信号帧。 对于视频来说,AVFrame就是视频的一帧图像。这帧图像什么时候显示给用户,就取决于它的PTS。DTS是AVPacket里的一个成员,表示这个压缩包应该什么时候被解码。 如果视频里各帧的编码是按输入顺序(也就是显示顺序)依次进行的,那么解码和显示时间应该是一致的。可事实上,在大多数编解码标准(如H.264或HEVC)中,编码顺序和输入顺序并不一致,于是才会需要PTS和DTS这两种不同的时间戳。所以视频流中的时间总是pts(显示时间) >= dts(解码时间)。

ffmpeg中时间相关时间单位:

        ffmepg中的内部计时单位(时间基),ffmepg中的所有时间都是于它为一个单位,比如AVStream中的duration即以为着这个流的长度为duration个AV_TIME_BASE。AV_TIME_BASE定义为:

#define         AV_TIME_BASE   1000000

ffmpeg提供了一个把AVRatioal结构转换成double的函数:

  1. static inline double av_q2d(AVRational a)
  2. /**
  3. * Convert rational to double.
  4. * @param a rational to convert
  5. **/
  6. return a.num / (double) a.den;
  7. }

可以根据pts来计算一桢在整个视频中的时间位置:

timestamp(秒) = pts * av_q2d(st->time_base);    //这里的st是一个AVStream对象指针。

计算视频长度的方法:

time(秒) = st->duration * av_q2d(st->time_base);    // 这里的st是一个AVStream对象指针。

时间基转换公式

  • timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
  • time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)

所以当需要把视频跳转到N秒的时候可以使用下面的方法:

  1. int64_t timestamp = N * AV_TIME_BASE; // N秒转换为内部时间戳
  2. av_seek_frame(fmtctx, index_of_video, timestamp, AVSEEK_FLAG_BACKWARD); // // AVSEEK_FLAG_BACKWARD 向后找到I帧

不同时间基之间的转换函数(作用是计算a * bq / cq,来把时间戳从一个时基调整到另外一个时基。在进行时基转换的时候,我们应该首选这个函数,因为它可以避免溢出的情况发生。)

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

裁剪音视频代码实例:

  1. //裁剪多媒体文件(因为视频存在I帧B帧P帧,所以裁剪结果和输入时长有误差)
  2. //编译链接:gcc -o cut cut.c `pkg-config --libs --cflags libavutil libavformat libavcodec`
  3. //执行 ./cut test.mp4 cut.mp4 (starttime) (endtime)(单位秒)
  4. #include<stdio.h>
  5. #include<stdlib.h>
  6. #include<libavutil/log.h>
  7. #include <libavformat/avformat.h>
  8. int main(int argc, char* argv[])
  9. {
  10. int ret = -1;
  11. int idx = -1;
  12. int i = 0;
  13. int stream_idx = 0;
  14. // 处理输入参数
  15. char* src, * dst;
  16. double starttime, endtime;
  17. int64_t* dts_start_time, * pts_start_time;
  18. int* stream_map = NULL;
  19. AVFormatContext* pFmtCtx = NULL; // 多媒体上下文
  20. AVFormatContext* oFmtCtx = NULL; // 目标文件上下文信息
  21. const AVOutputFormat* outFmt = NULL; // 输出文件格式信息
  22. AVPacket pkt; // 包
  23. av_log_set_level(AV_LOG_DEBUG);
  24. if (argc < 5) { //该可执行程序 源文件 目标文件 起始时间 结束时间
  25. av_log(NULL, AV_LOG_INFO, "Arguments must be more than 5.");
  26. exit(-1);
  27. }
  28. src = argv[1];
  29. dst = argv[2];
  30. starttime = atof(argv[3]);
  31. endtime = atof(argv[4]);
  32. if (endtime < starttime) {
  33. av_log(NULL, AV_LOG_INFO, "Cut time error!.");
  34. exit(-1);
  35. }
  36. // 打开多媒体文件(包含文件头和文件体)
  37. if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL)))
  38. {
  39. av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
  40. exit(-1);
  41. }
  42. // 打开目的文件的上下文
  43. avformat_alloc_output_context2(&oFmtCtx, NULL, NULL, dst);
  44. if (!oFmtCtx) {
  45. av_log(NULL, AV_LOG_ERROR, "NO Memory!\n");
  46. goto _ERROR;
  47. }
  48. stream_map = av_calloc(pFmtCtx->nb_streams, sizeof(int));
  49. if (!stream_map) {
  50. av_log(NULL, AV_LOG_ERROR, "NO Memory!\n");
  51. goto _ERROR;
  52. }
  53. // 遍历源文件每一条流
  54. for (i = 0; i < pFmtCtx->nb_streams; i++) {
  55. AVStream* outStream = NULL;
  56. AVStream* inStream = pFmtCtx->streams[i];
  57. AVCodecParameters* inCodecPar = inStream->codecpar;
  58. // 只处理音、视频、字幕数据
  59. if (inCodecPar->codec_type != AVMEDIA_TYPE_AUDIO &&
  60. inCodecPar->codec_type != AVMEDIA_TYPE_VIDEO &&
  61. inCodecPar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
  62. stream_map[i] = -1;
  63. continue;
  64. }
  65. stream_map[i] = stream_idx++;
  66. // 为目的文件创建一个新的视频流
  67. outStream = avformat_new_stream(oFmtCtx, NULL);
  68. if (!outStream) {
  69. av_log(oFmtCtx, AV_LOG_ERROR, "NO Memory!\n");
  70. goto _ERROR;
  71. }
  72. avcodec_parameters_copy(outStream->codecpar, inStream->codecpar); //将源文件的内容复制到目的文件
  73. outStream->codecpar->codec_tag = 0; // 根据多媒体文件自动识别编解码器
  74. }
  75. //上下文信息与输出文件绑定
  76. ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);
  77. if (ret < 0) {
  78. av_log(NULL, AV_LOG_ERROR, "%s", av_err2str(ret));
  79. goto _ERROR;
  80. }
  81. // 写多媒体文件头(包含多媒体的类型、版本等信息)到目标文件
  82. ret = avformat_write_header(oFmtCtx, NULL);
  83. if (ret < 0) {
  84. av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));
  85. goto _ERROR;
  86. }
  87. // 跳转到时间点
  88. ret = av_seek_frame(pFmtCtx, -1, starttime * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD); // AVSEEK_FLAG_BACKWARD 向后找到I帧
  89. if (ret < 0) {
  90. av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));
  91. goto _ERROR;
  92. }
  93. // 记录第一个包的时间戳
  94. dts_start_time = av_calloc(pFmtCtx->nb_streams, sizeof(int64_t));
  95. pts_start_time = av_calloc(pFmtCtx->nb_streams, sizeof(int64_t));
  96. for (int t = 0; t < pFmtCtx->nb_streams; t++) {
  97. dts_start_time[t] = -1;
  98. pts_start_time[t] = -1;
  99. }
  100. // 从源多媒体文件中读到音、视频、字幕数据
  101. while (av_read_frame(pFmtCtx, &pkt) >= 0) { // 从多媒体文件读取到帧数据,读取码流中的音频若干帧或者视频一帧
  102. AVStream* inStream, * outStream;
  103. // 记录每组流截取开始的时间戳
  104. if (dts_start_time[pkt.stream_index] == -1 && pkt.dts > 0) {
  105. dts_start_time[pkt.stream_index] = pkt.dts;
  106. }
  107. if (pts_start_time[pkt.stream_index] == -1 && pkt.pts > 0) {
  108. pts_start_time[pkt.stream_index] = pkt.pts;
  109. }
  110. inStream = pFmtCtx->streams[pkt.stream_index];
  111. if (av_q2d(inStream->time_base) * pkt.pts > endtime) { // 结束时间
  112. av_log(oFmtCtx, AV_LOG_INFO, "cut success!\n");
  113. break;
  114. }
  115. if (stream_map[pkt.stream_index] < 0) { // 流编号为-1, 不是音、视频、字幕流数据
  116. av_packet_unref(&pkt); // 释放packet
  117. continue;
  118. }
  119. // 相对时间
  120. pkt.pts = pkt.pts - pts_start_time[pkt.stream_index];
  121. pkt.dts = pkt.dts - dts_start_time[pkt.stream_index];
  122. if (pkt.dts > pkt.pts) { // 音频dts、pts 相等,视频的pts >= dts
  123. pkt.pts = pkt.dts;
  124. }
  125. pkt.stream_index = stream_map[pkt.stream_index];
  126. outStream = oFmtCtx->streams[pkt.stream_index];
  127. av_packet_rescale_ts(&pkt, inStream->time_base, outStream->time_base); // 修改时间戳
  128. pkt.pos = -1; // 偏移位置
  129. av_interleaved_write_frame(oFmtCtx, &pkt); // 将视频帧写入目标文件中
  130. av_packet_unref(&pkt);
  131. }
  132. // 写多媒体文件尾到文件中
  133. av_write_trailer(oFmtCtx);
  134. // 将申请的资源释放掉
  135. _ERROR:
  136. if (pFmtCtx) {
  137. avformat_close_input(&pFmtCtx);
  138. pFmtCtx = NULL;
  139. }
  140. if (oFmtCtx->pb) {
  141. avio_close(oFmtCtx->pb);
  142. }
  143. if (oFmtCtx) {
  144. avformat_free_context(oFmtCtx);
  145. oFmtCtx = NULL;
  146. }
  147. if (stream_map) {
  148. av_free(stream_map);
  149. }
  150. if (dts_start_time) {
  151. av_free(dts_start_time);
  152. }
  153. if (pts_start_time) {
  154. av_free(pts_start_time);
  155. }
  156. return 0;
  157. }

参考:

ffmpeg中的时间单位_pkt.duration的值-CSDN博客

https://blog.51cto.com/moonfdd/6266754?articleABtest=0

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/833953
推荐阅读
相关标签
  

闽ICP备14008679号