FFmpeg 音视频倍速控制

网上关于FFmpeg音视频倍速控制的资料不多,大部分都是讲通过FFmpeg命令去做音视频文件的倍速处理,通过FFmpeg api去 处理倍速的资料少之又少。

本文除了会讲到通过命令行处理倍速,还会讲到通过FFmpeg api的方式去处理音频倍速和视频倍速,进而合并成支持倍速的音视频发布成rtmp或者存成flv文件。

介绍FFmpeg的filter工具

音视频的倍速处理少不了filter这个工具,filter可以翻译成过滤器和滤波器。

按照处理数据的类型还可分为音频filter、视频filter、字幕filter,FFmpeg可以通过filter将音视频实现出非常多不同的filter效果,视频可以实现缩放、合并、裁剪、旋转、水印添加、倍速等效果,音频可以实现回声、延迟、去噪、混音、音量调节、变速等效果。

我们可以通过filter不同方式的组合去定制出我们想要的音视频特效

介绍FFmpeg命令行倍速

1、视频的倍速主要是通过控制filter中的setpts来实现,setpts是视频滤波器通过改变每一个pts时间戳来实现倍速的效果,如下只要把PTS缩小一半就可以实现2倍速,相反的是PTS增加一倍就达到2倍慢放的效果。

ffmpeg -i in.mp4 -filter:v "setpts=0.5*PTS" out.mp4
ffmpeg -i in.mp4 -filter:v "setpts=2.0*PTS" out.mp4

2、音频的倍速则是通过控制filter的atempo来实现,atempo的配置区间在0.5和2.0之间,如果需要更高倍速,则需要多个atempo串一起,下面是2、4倍速的实现命令。

ffmpeg -i in.mp4 -filter:"atempo=2.0" -vn out.mp4
ffmpeg -i in.mp4 -filter:"atempo=2.0,atempo=2.0" -vn out.mp4

3、同时对音视频进行2倍速。

ffmpeg -i in.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" out.mp4

视频倍速

视频倍速可以通过filter的setpts来更改视频的倍速,但是filter只对未编码的原始视频数据进行倍速处理。

我这边对于视频的处理是将H264进行格式封装,如果使用filter的话,我还需要先对H264解码,再做倍速转换,再重新编码成H264,这样将会消耗相当多的资源。

我这边对视频倍速就没必要用filter进行处理。其实视频倍速只需要改变封装格式时设置的时间戳就可以,所以我这边只要改变video_tempo这个参数的值即可,1000是正常倍速、2000是2倍速,依次叠加。

if (tempo != video_tempo && (tempo >= 1000 && tempo <= 16000)) {
  video_tempo = tempo;
  Logger::Info("video_tempo = %d", video_tempo);
}
 AVRational time_base = { 1, video_tempo};
 pkt.pts = av_rescale_q((ptsInc++) * 2, time_base, ost->st->time_base);
 pkt.dts = av_rescale_q_rnd(pkt.dts, ost->st->time_base, ost->st->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
 pkt.duration = av_rescale_q(pkt.duration, ost->st->time_base, ost->st->time_base);

音频倍速

音频倍速就需要用到filter进行处理了,我这边对音频的处理是,先解码音频,然后用filter进行倍速处理,对处理后的音频数据进行重采样再编码成AAC的音频格式。

如果只是对音频做处理,不做音视频封装,可以直接把filter处理后的数据存成PCM,即可验证音频的倍速。

  1. 初始化filter的abuffer,aformat,abuffersink滤波器,abuffer用于接收输入frame,形成待处理的数据缓存,abuffersink用于传出输出Frame,aformat过滤器约束最终的输出格式(采样率,声道数,存储位数等),而中间的其他过滤器可以串联多个filter,如volume(音量调节),atempo(变速)等。

int init_atempo_filter(AVFilterGraph **pGraph, AVFilterContext **src, AVFilterContext **out, char *value)
{
 //初始化AVFilterGraph
 AVFilterGraph *graph = avfilter_graph_alloc();
 //获取abuffer用于接收输入端
 const AVFilter *abuffer = avfilter_get_by_name("abuffer");
 AVFilterContext *abuffer_ctx = avfilter_graph_alloc_filter(graph, abuffer, "src");
 //设置参数,这里需要匹配原始音频采样率8000、数据格式(位数:16)、单声道
 if (avfilter_init_str(abuffer_ctx, "sample_rate=8000:sample_fmt=s16:channel_layout=mono") < 0) {
  Logger::Error("error init abuffer filter");
  return -1;
 }
 //初始化atempo filter
 const AVFilter *atempo = avfilter_get_by_name("atempo");
 AVFilterContext *atempo_ctx = avfilter_graph_alloc_filter(graph, atempo, "atempo");
 //这里采用av_dict_set设置参数
 AVDictionary *args = NULL;
 av_dict_set(&args, "tempo", value, 0);//这里传入外部参数,可以动态修改
 if (avfilter_init_dict(atempo_ctx, &args) < 0) {
  Logger::Error("error init atempo filter");
  return -1;
 }

 const AVFilter *aformat = avfilter_get_by_name("aformat");
 AVFilterContext *aformat_ctx = avfilter_graph_alloc_filter(graph, aformat, "aformat");
 if (avfilter_init_str(aformat_ctx, "sample_rates=8000:sample_fmts=s16:channel_layouts=mono") < 0) {
  Logger::Error("error init aformat filter");
  return -1;
 }
 //初始化sink用于输出
 const AVFilter *sink = avfilter_get_by_name("abuffersink");
 AVFilterContext *sink_ctx = avfilter_graph_alloc_filter(graph, sink, "sink");
 if (avfilter_init_str(sink_ctx, NULL) < 0) {//无需参数
  Logger::Error("error init sink filter");
  return -1;
 }
 //链接各个filter上下文
 if (avfilter_link(abuffer_ctx, 0, atempo_ctx, 0) != 0) {
  Logger::Error("error link to atempo filter");
  return -1;
 }
 if (avfilter_link(atempo_ctx, 0, aformat_ctx, 0) != 0) {
  Logger::Error("error link to aformat filter");
  return -1;
 }
 if (avfilter_link(aformat_ctx, 0, sink_ctx, 0) != 0) {
  Logger::Error("error link to sink filter");
  return -1;
 }
 if (avfilter_graph_config(graph, NULL) < 0) {
  Logger::Error("error config filter graph");
  return -1;
 }
 *pGraph = graph;
 *src = abuffer_ctx;
 *out = sink_ctx;
 Logger::Info("init filter success...");
 return 0;
}
  1. 注册所有过滤器

avfilter_register_all();
 if (init_atempo_filter(&s_graph, &in_ctx, &out_ctx, "1.0") != 0) {
  Logger::Error("Codec not init audio filter");
  return -1;
 }
  1. 使用过滤器 将解码后的AVFrame通过av_buffersrc_add_frame(in_ctx, pFrameAudio)加入到输入过滤器上下文in_ctx中,通过av_buffersink_get_frame(out_ctx, pFrameAudio)获取处理完成的pFrameAudio。pFrameAudio就可以直接存成PCM或者再编码封装音视频格式。

//将pFrameAudio放入输入filter上下文
 if (av_buffersrc_add_frame(in_ctx, pFrameAudio) < 0) {
  Logger::Error("error add frame");
 }
 //从输出filter上下文中获取pFrameAudio
 while (av_buffersink_get_frame(out_ctx, pFrameAudio) >= 0) {
  break;
 }

动态修改倍速说明

本文由于封装音视频倍速的时候,视频倍速处理是没有通过filter去做的,只是改变视频帧封装的时间戳达到倍速效果.如果最开始是以正常速度的倍速推流,过程中再去修改倍速参数,就会导致要写进的视频时间戳大于之前的时间戳,进而出错。

所以本文讲的ffmpeg的API控制音视频倍速是没办法通过动态调整倍速的。必须一开始就设置好倍速,后续就不能再改动。如果单独对音频进行动态修改倍速,则是没问题的。

作者:RzzZ

来源:https://blog.csdn.net/qq_22633333/article/details/104967304

908994f5f64fe26d159faca97ca25312.png

06f6aef56fc796023f0a074804859a3d.png

一个音视频领域专业问答的小圈子!

推荐阅读:

音视频开发工作经验分享 || 视频版

OpenGL ES 学习资源分享

开通专辑 | 细数那些年写过的技术文章专辑

Android NDK 免费视频在线学习!!!

你想要的音视频开发资料库来了

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

3df4cf596fce36b50f637482d9b026b4.gif

### 回答1: FFmpeg是一个开源的多媒体框架,可以处理音视频的编解码、转码、流媒体等功能。在播放接收的组播数据时,可以使用FFmpeg提供的接口来完成。 首先,我们需要使用FFmpeg建立一个输入流,以接收组播数据。可以通过指定输入的URL或者IP地址来接收组播数据。例如,假设组播地址是239.255.0.1,端口号是1234,可以使用以下命令建立输入流: ffmpeg -i udp://@239.255.0.1:1234 接下来,FFmpeg会自动解析组播数据的格式,并将其转换成可播放的音视频流。这样,我们就可以使用FFmpeg提供的播放器来播放组播数据了。 另外,我们还可以使用FFmpeg提供的功能对接收的组播数据进行处理。例如,可以使用FFmpeg的滤镜功能来实现实时的图像处理、特效添加等。可以通过在命令中指定-filter_complex参数来使用滤镜功能。例如,以下命令将接收的组播数据使用黑白滤镜处理后播放: ffmpeg -i udp://@239.255.0.1:1234 -vf "hue=s=0" -f sdl "Output Window" 上述命令中,-vf参数指定了使用hue滤镜将组播数据转换成黑白效果,-f参数指定了输出窗口类型为SDL,最后的参数指定了输出窗口的名称为"Output Window"。 总之,使用FFmpeg播放接收的组播数据可以通过建立输入流来接收数据,并使用FFmpeg提供的播放器来进行播放。同时,还可以通过使用FFmpeg提供的滤镜功能对接收的组播数据进行处理。 ### 回答2: ffmpeg 是一种功能强大的开源多媒体处理工具,除了可以用来转码、剪辑、合并视频文件外,它还具备播放接收的组播数据的功能。 在使用 ffmpeg 播放接收的组播数据之前,需要先了解组播数据的特点。组播数据是一种基于 IP 多播技术的数据传输方式,在网络中可以同时传输给多个接收者。组播数据通常使用 UDP 协议进行传输,并使用特定的组播 IP 地址进行标识。 使用 ffmpeg 播放接收的组播数据,首先需要指定组播 IP 地址和端口号。可以使用以下命令来指定组播 IP 地址和端口号: ffmpeg -i udp://组播IP地址:端口号 这样,ffmpeg 就会从指定的组播 IP 地址和端口号接收数据,并进行播放。在播放过程中,ffmpeg 会自动解析接收到的组播数据,并将其转换为可播放的音视频流。 另外,如果需要将接收的组播数据保存为文件,可以使用以下命令: ffmpeg -i udp://组播IP地址:端口号 -c copy 输出文件名 这样,ffmpeg 就会将接收到的组播数据保存为指定的文件。 需要注意的是,使用 ffmpeg 播放接收的组播数据时,要确保网络中正常传输组播数据的设备和网络配置正确。此外,组播数据的传输速度也会受到网络带宽和设备性能的限制,可能会影响播放的流畅度和质量。 总而言之,使用 ffmpeg 可以方便地播放接收的组播数据,只需指定组播 IP 地址和端口号即可。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值