ffmpeg-音视频精准截取

前言

有时会碰到这样的需求场景,对一个视频中的某一段感兴趣,想要精确的截取这一段视频以及对应的音频。例如,有一个25fps的MP4的文件,时长20秒,我想要截取从5秒开始到15秒结束的视频以及对应的音频,这里有两点需要说明: 1、对于视频:开始时间5秒,结束时间15秒。只能做到尽量接近,因为源文件25fps,即每一帧的显示间隔为0.04秒,可能5秒附近的视频帧刚好在5.012秒,最大误差一帧时间差就是1/25==0.04秒,所以4.96-5.04秒范围内都可以。 2、对于音频:比如采样率44100,每一个AVPacket包含1024个采样,那么每一帧的显示时间间隔为1/43≈0.025秒,音频再5秒处的最大误差为0.025秒,所以4.975-5.025秒范围内都可以。

这里以MP4文件为例,假设视频的编码方式为H264,音频为aac,其它格式类似。

实现思路分析

1、在ffmpeg解封装之后得到的AVPacket代表着压缩的音/视频数据,该结构体内有一个字段pts,表示了该音/视频的时间,即前面说的5秒就是要参考这个时间。 2、对于音频来说,只需要依次取出5秒(或者最接近5秒)到15秒处的AVPacket,然后调用ffmpeg封装接口依次写入新的MP4文件即可 3、对于视频来说,写入MP4的第一个AVPacket一定要是I帧(对于H264等有IPB帧改变的编码方式来说,其它编码方式则跟音频一样处理),所以视频的处理分两种情况:

  • 当5秒(或者最接近5秒)处的AVPacket刚好为I帧,那么只需依次取出直到15秒处的AVpacket,然后依次写入MP4文件即可;

  • 当5秒处的AVPacket非I帧,那么就需要往前找出最近一个I帧的AVPacket,然后用这个AVPacket往后依次解码,直到解码出5秒处的AVPacket,然后将解码得到AVFrame重新进行编码为AVPacket(那么5秒处的AVPacket就一定是I帧了)再写入MP4文件,5秒后的视频也按照先解码再编码为AVPacket再写入MP4文件的思路进行

流程图

img

 

关键函数

  • int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);

调用av_read_frame()函数时,其内部会有一个文件指针(默认情况下指向文件中的首个AVPacket),所以默认时读取的第一个AVPacket就是文件中的首个AVPacket,调用结束后指针再指向下一个AVPacket。此函数的作用就相当于将指针指向指定的key_frame的AVPacket,那么首次调用av_read_frame()函数时将获取的是满足要求的AVPacket,而非文件的第一个AVPacket。

1、stream_index:代表音视频流的索引,如果为-1,那么将对文件的默认流索引进行操作 2、timestamp:移动到指定的时间戳(单位为AVStream.time_base )。如果stream_index为-1,单位为(AV_TIME_BASE) 3、flag:移动参考的方式,取值如下: #define AVSEEK_FLAG_BACKWARD 1 ///< seek backward #define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes #define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes #define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number

AVSEEK_FLAG_BACKWARD 基于前面指定的时间戳参数查找,如果timestamp处的AVPacket的key_frame非1,那么就往前找,直到找到最近一个key_frame为1的AVPacket

AVSEEK_FLAG_BYTE 基于位置进行查找

AVSEEK_FLAG_ANY 基于前面指定的时间戳参数进行查找,不管AVPacket是否key_frame为1都返回

AVSEEK_FLAG_FRAME 基于帧编号进行查找

实现代码

#include <stdio.h>
#include <string>
#include <iomanip>
#include <chrono>
#include "CLog.h"
#include <iostream>
extern "C"
{
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
}
using namespace std;
​
// 打开 .hpp line 33 .cpp line 100
class Cut
{
public:
   /** 实现时间的精准裁剪
    */
   void doCut();
private:
   
   int64_t start_pos;
   int64_t video_start_pts;
   int64_t audio_start_pts;
   int64_t duration;           // 时长 单位秒
   int64_t video_next_pts;
   int64_t audio_next_pts;
   
   // 源文件
   string srcPath;
   // 目标文件
   string dstPath;
   // 视频流索引
   int video_in_stream_index,video_ou_tream_index;
   // 音频流索引
   int audio_in_stream_index,audio_ou_stream_index;
   bool has_writer_header;
   // 用于解封装
   AVFormatContext *in_fmtctx;
   // 用于封装
   AVFormatContext *ou_fmtctx;
   // 视频编码和解码用
   AVFrame *video_de_frame;
   AVFrame *video_en_frame;
   AVCodecContext *video_de_ctx;
   AVCodec
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农老K

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值