自学内容网 自学内容网

基于ffmepg的视频剪辑

1.ffmpeg命令实现视频剪辑

  FFmpeg是一个非常强大的视频处理工具,可以用来剪辑视频。以下是一个基本的FFmpeg命令行示例,用于剪辑视频:

$ ffmpeg -i ./最后一滴水.mp4  -ss 0:0:20  -t 50  -c copy output.mp4

  -i ./最后一滴水.mp4 输入文件
  -ss 0:0:20 剪辑的起始时间20s
  -t 50 剪辑的时长50s,也可以写成时分秒格式:00:00:50
  -c copy 复制编码以避免重编码,加快处理速度。
  output.mp4 输出文件

2.调用ffmpeg库实现视频剪辑

  • 音视频裁剪步骤

  (1)打开源媒体文件avformat_open_input
  (2)读取数据包,获取流信息avformat_find_stream_info,输出流信息av_dump_format
  (3)创建输出上下文avformat_alloc_output_context2
  (4)获取源文件中的流数据,拷贝到目标文件avcodec_parameters_copy
  (5)打开输出文件上下文avio_open
  (6)写入头数据avformat_write_header
  (7)设置到剪切的位置av_seek_frame
  (8)循环读取数据跑av_read_frame,对读取的数据包时间转换av_packet_rescale_ts,写入到目的媒体文件av_interleaved_write_frame
  (9)判断截取的尾时间av_q2d(istream->time_base)*pkt.pts >= end_time
  (10)写入文件尾数据av_write_trailer
  (11)释放资源

  • 流程图如下:
    在这里插入图片描述
#include <stdio.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
#include <libavutil/rational.h>
#include <libavformat/avformat.h>
#include <stdlib.h>

//音视频裁剪
int main(int argc,char **argv)
{
av_log_set_level(AV_LOG_DEBUG);
if(argc!=5)
{
av_log(NULL,AV_LOG_INFO,"格式:./app <源文件> <目标文件> <起始时间> <结束时间>\n");
return 0;
}
char *src=argv[1];//源文件
char *dst=argv[2];//目标文件
double start_time=atof(argv[3]);//起始时间
double end_time=atof(argv[4]);//结束时间
av_log(NULL,AV_LOG_INFO,"start=%.1f\t end=%.1f\n",start_time,end_time);
if(end_time<=start_time)
{
av_log(NULL,AV_LOG_ERROR,"裁剪的结束时间应大于起始时间\n");
return 0;
}
//1.打开源媒体文件
AVFormatContext *pfmtctx=NULL;
AVFormatContext *ofmtctx=NULL;
int64_t *start_time_dts=NULL;//保存每路流的dts起始时间
int64_t *start_time_pts=NULL;//保存每路流的pts起始时间
int *stream_arr=NULL;//用于保存有效流的下标
int ret=avformat_open_input(&pfmtctx, src,NULL,NULL);
if(ret!=0){
av_log(NULL,AV_LOG_ERROR,"打开源媒体文件失败ret=%s\n",av_err2str(ret));
return 0;
}
//2.读取数据包,获取流信息
ret=avformat_find_stream_info(pfmtctx, NULL);
if(ret<0){
av_log(NULL,AV_LOG_ERROR,"获取流信息失败ret=%s\n",av_err2str(ret));
goto _fil;
}
//输出流信息
//av_dump_format(AVFormatContext * ic, int index, const char * url, int is_output)
av_log(pfmtctx,AV_LOG_INFO,"文件名:%s\n",pfmtctx->iformat->name);
//时长
 if (pfmtctx->duration != AV_NOPTS_VALUE) {
            int64_t hours, mins, secs, us;
            int64_t duration = pfmtctx->duration + (pfmtctx->duration <= INT64_MAX - 5000 ? 5000 : 0);
            secs  = duration / AV_TIME_BASE;
            us    = duration % AV_TIME_BASE;
            mins  = secs / 60;
            secs %= 60;
            hours = mins / 60;
            mins %= 60;
            av_log(NULL, AV_LOG_INFO, "播放时长:%02"PRId64":%02"PRId64":%02"PRId64".%02"PRId64"\n", hours, mins, secs,
                   (100 * us) / AV_TIME_BASE);
      } 
 //3.创建目标媒体文件上下文件
 ret=avformat_alloc_output_context2(&ofmtctx,NULL,NULL, dst);
 if(ret<0){
av_log(NULL,AV_LOG_ERROR,"创建输出媒体文件上下文失败ret=%s\n",av_err2str(ret));
goto _fil;
 }
 //4.读取源文件的所有流数据
 stream_arr=av_calloc(pfmtctx->nb_streams, sizeof(int));//用于保存流下标
 int stream_index=0;
 for(int i=0;i<pfmtctx->nb_streams;i++)

 {
AVStream *istream=pfmtctx->streams[i];//输入流数据
AVStream *ostream=NULL;
if(istream->codecpar->codec_type!=AVMEDIA_TYPE_VIDEO &&  //视频
   istream->codecpar->codec_type!=AVMEDIA_TYPE_AUDIO &&  //音频
   istream->codecpar->codec_type!=AVMEDIA_TYPE_SUBTITLE )  //字幕
{
stream_arr[i]=-1;//将除此之外的流下标置为-1
continue;
}
stream_arr[i]=stream_index++;//正常流下标从0开始
//创建一个输出流
ostream=avformat_new_stream(ofmtctx,NULL);
if(ostream==NULL){
av_log(ofmtctx,AV_LOG_ERROR,"创建输出流失败\n");
goto _fil;
}
//将源文件流数据拷贝到目标文件
ret=avcodec_parameters_copy(ostream->codecpar, istream->codecpar);
if(ret<0){
av_log(ofmtctx,AV_LOG_ERROR,"拷贝流数据失败\n");
goto _fil;
}
istream->codecpar->codec_tag=0;//解码器标志,填0表示由系统决定
 }
 //5.打开目标文件
 ret=avio_open(&ofmtctx->pb, dst,AVIO_FLAG_READ_WRITE);
 if(ret<0){
av_log(ofmtctx,AV_LOG_ERROR,"打开目标文件失败ret=%s\n",av_err2str(ret));
goto _fil;
 }
 //6.写入文件头数据
 ret=avformat_write_header(ofmtctx, NULL);
 if(ret<0){
av_log(&ofmtctx,AV_LOG_ERROR,"写入头数据失败err=%s\n",av_err2str(ret));
goto _fil;
 }
 /*
 7.设置要截取的起始位置
int av_seek_frame(AVFormatContext *s, int stream_index,int64_t timestamp, int flags)
形参:s --媒体文件上下文指针
      stream_index --流下标位置,填-1,时间戳会自动从AV_TIME_BASE单位转换为流特定的时基
      timestamp --要跳转到的位置,用设置的秒时间*AV_TIME_BASE
      flags  --查找流数据帧的处理方式,AVSEEK_FLAG_BACKWARD表示向后查找到关键帧(这对视频来说很重要)
返回值:成功返回>=0

AV_TIME_BASE -->该参数是ffmpeg内部的时间基准,pts、dts 、duration等均需使用该参数转换
 */
 ret=av_seek_frame(pfmtctx,-1,start_time*AV_TIME_BASE ,AVSEEK_FLAG_BACKWARD);
 if(ret){
av_log(&pfmtctx,AV_LOG_ERROR,"跳转到指定位置失败err=%s\n",av_err2str(ret));
goto _fil;
 }
 
 start_time_dts=av_calloc(pfmtctx->nb_streams, sizeof(int64_t));
 start_time_pts=av_calloc(pfmtctx->nb_streams, sizeof(int64_t));
 for(int i=0;i<pfmtctx->nb_streams;i++)
 {
start_time_dts[i]=-1;//将默认值初始化为-1
start_time_pts[i]=-1;
 }
 //8.从源文件中读取数据流
 AVPacket pkt;
 while(av_read_frame(pfmtctx,&pkt)==0)
 {
//判断读取的流是否为我们需要的流数据
AVStream *istream=pfmtctx->streams[pkt.stream_index];//输入流数据
AVStream *ostream=NULL;
if(stream_arr[pkt.stream_index]==-1)//不需要的流数据
{
av_packet_unref(&pkt);//释放包
continue;
}
if(istream->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
{
av_log(pfmtctx,AV_LOG_INFO,"pts=%.1f s\n",av_q2d(istream->time_base)*pkt.pts);//显示pts时间
}
if(av_q2d(istream->time_base)*pkt.pts>=end_time)break;//判断是否结束时间到
if(start_time_dts[pkt.stream_index]==-1 && pkt.dts>0)
{
start_time_dts[pkt.stream_index]=pkt.dts;//保存截取的起始时间dts
}

if(start_time_pts[pkt.stream_index]==-1 && pkt.pts>0)
{
start_time_pts[pkt.stream_index]=pkt.pts;//保存截取的起始时间pts
}
pkt.dts-=start_time_dts[pkt.stream_index];//解码时间戳
pkt.pts-=start_time_pts[pkt.stream_index];//显示时间戳
if(pkt.pts<pkt.dts)
{
pkt.pts=pkt.dts;//显示时间戳pts>=解码时戳dts
}
pkt.stream_index=stream_arr[pkt.stream_index];//当前流下标
ostream=ofmtctx->streams[pkt.stream_index];//输出流

//时间转换:dts、pts、durations
av_packet_rescale_ts(&pkt,istream->time_base, ostream->time_base);
pkt.pos=-1;//相对位置
av_interleaved_write_frame(ofmtctx, &pkt);
av_packet_unref(&pkt);//减少引用次数

 }
 //写入文件尾数据
 av_write_trailer(ofmtctx);

_fil:
avformat_close_input(&pfmtctx);
avformat_free_context(ofmtctx);
av_free(start_time_dts);
av_free(start_time_pts);
av_free(stream_arr);
}

原文地址:https://blog.csdn.net/weixin_44453694/article/details/140545704

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!