FFmpeg给视频添加图片水印过程很慢
FFmpeg adding image watermark to video process is very slow
我在 FFmpeg 的帮助下向视频添加图像水印,但 FFmpeg 使用以下命令花费了过多的时间-
String[] cmd = {"-i",videoPath, "-i", waterMark.toString(),"-filter_complex","overlay=5:5","-codec:a", "copy", outputPath};
所以我尝试了另一个命令,它稍微快一点但增加了输出文件大小(我不想要)
String[] cmd = {"-y","-i", videoPath, "-i", waterMark.toString(), "-filter_complex", "overlay=5:5", "-c:v","libx264","-preset", "ultrafast", outputPath};
请有人向我解释如何在不增加输出大小的情况下提高 FFmpeg 水印速度。
谢谢
您提到 7MB 的视频需要 30-60 秒。
在速度和质量之间做出选择时总是需要权衡取舍。
我在 phone 上使用一个 7MB 的文件进行了测试,它花了 13 秒,仍然很慢,但我们不能期望比这更好。
提高速度的方法:
- 降低帧率,使用
-r
命令
- 更改比特率,使用
-b:v
和 -b:a
命令
- 使用
-crf
更改恒定速率因子。默认值为 21
The range of the quantizer scale is 0-51: where 0 is lossless, 23 is default, and 51 is worst possible. A lower value is a higher quality and a subjectively sane range is 18-28. Consider 18 to be visually lossless or nearly so: it should look the same or nearly the same as the input but it isn't technically lossless.
这是我发现在大多数 android 设备上效果最好的:
String[] s = {"-i", VideoPath, "-i", ImagePath, "-filter_complex", "[0:v]pad=iw:if(lte(ih\,iw)\,ih\,2*trunc(iw*16/9/2)):(ow-iw)/2:(oh-ih)/2[v0];[1:v][v0]scale2ref[v1][v0];[v0][v1]overlay=x=(W-w)/2:y=(H-h)/2[v]", "-map", "[v]", "-map", "0:a", "-c:v", "libx264", "-preset", "ultrafast", "-r", myFrameRate, directoryToStore[0] + "/" + SavedVideoName};
我稍微降低了帧率,你可以尝试一下最适合你的。我正在使用 mp4parser
检索帧速率。
我必须感谢@Gyan,他为我提供了一种完美缩放放置在视频顶部的图像的方法,你可以看看我问的问题。
如果您不确定帧率,您可以将其从命令中删除并首先测试您的速度是否降低。
试一试,有问题欢迎追问
OP 选择使用以下命令:
String[] cmd = {"-y","-i", videoPath, "-i", waterMark.toString(), "-filter_complex", "overlay=(main_w-overlay_w-10):5", "-map", "0:a","-c:v", "libx264", "-crf", "28","-preset", "ultrafast" ,outputPath};
编辑
只是添加我提到的命令并提供如何使用它等的详细说明:
String[] cmd = {"-i", videoPath, "-i", waterMark.toString(), "-filter_complex", "[0:v]pad=iw:if(lte(ih\,iw)\,ih\,2*trunc(iw*16/9/2)):(ow-iw)/2:(oh-ih)/2[v0];[1:v][v0]scale2ref[v1][v0];[v0][v1]overlay=x=(W-w)/2:y=(H-h)/2[v]", "-map", "[v]", "-map", "0:a", "-c:v", "libx264", "-preset", "ultrafast", "-r", myFrameRate, outputPath};
这适用于显示纵横比为 16:9
的设备。如果您希望此滤镜适用于所有设备,则必须获取设备的宽高比并分别更改滤镜 16/9/2
。
您可以通过创建此方法来获取设备纵横比:
int gcd(int p, int q) {
if (q == 0) return p;
else return gcd(q, p % q);
}
void ratio(int a, int b) {
final int gcd = gcd(a,b);
if(a > b) {
setAspectRatio(a/gcd, b/gcd);
} else {
setAspectRatio(b/gcd, a/gcd);
}
}
void setAspectRatio(int a, int b) {
System.out.println("aspect ratio = "+a + " " + b);
//This is the string that will be used in the filter (instead of hardcoding 16/9/2
filterAspectRatio = a + "/" + b + "/" + "2";
}
现在您有了正确的纵横比,您可以相应地更改滤镜。
接下来,创建一个水印并将其添加到视图中,使该视图大小与设备相同 (match_parent
),并且 scale/place 水印位于您希望的位置。然后您可以通过调用获取位图:
Bitmap waterMarkBitmap = watermarkView.getDrawingCache();
并从 Bitmap
创建一个文件,如下所示:
String outputFilename = "myCoolWatermark.png"; //provide a name for you saved watermark
File path = Environment.getExternalStorageDirectory(); //this can be changed to where you want to store the bitmap
File waterMark = new File(path, outputFilename);
try (FileOutputStream out = new FileOutputStream(waterMark)) {
waterMarkBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); // PNG is a lossless format, the compression factor (100) is ignored
} catch (IOException e) {
e.printStackTrace();
}
水印已创建并可以重复使用,或者您可以在完成后将其删除。
现在可以调用上面的命令了
这是一个很常见的问题。 简单的答案是,您无法在 Android 上提高 ffmpeg
的编码速度。 您正在 phone 上编码,所以您不要指望 desktop/server 使用编码器和不支持硬件加速的性能。
用户可以做几件事:
Stream copy 带有 -c:a copy
的音频(您已经这样做了)。
用-preset ultrafast
为了编码速度放弃编码效率(你也已经这样做了)
使用 scale 过滤器使输出宽度 x 高度变小(可能不是您可以接受的选项)。
确保您的 x264 未使用 --disable-asm
编译,以便您可以利用 x264 中的各种 ARM 和 NEON 优化来显着提高编码速度。但是,我不知道哪些 Android 设备支持该功能,但值得研究。要快速检查您是否正在使用任何优化,请参考 ffmpeg
的控制台输出并搜索 using cpu capabilities
。如果 none!
那么它没有使用任何优化,否则它可能会说 ARMv7 NEON
或类似的东西。
将编码卸载到服务器。也可以节省用户的电池寿命。
这一切都是为了一个恼人的水印?避免重新编码,使用播放器叠加水印。
显然FFmpeg has MediaCodec decoding support on Android,但编码是这里的瓶颈。但是也许它会节省几帧。
向 FFmpeg 发送补丁以启用 MediaCodec 编码支持,或者等待几年让其他人这样做。
忘记ffmpeg
,直接使用MediaCodec。我对此一无所知,也懒得查找,但我认为它使用硬件进行编码,我猜你可以用它来制作覆盖图。如果我错了有人纠正我。
我在 FFmpeg 的帮助下向视频添加图像水印,但 FFmpeg 使用以下命令花费了过多的时间-
String[] cmd = {"-i",videoPath, "-i", waterMark.toString(),"-filter_complex","overlay=5:5","-codec:a", "copy", outputPath};
所以我尝试了另一个命令,它稍微快一点但增加了输出文件大小(我不想要)
String[] cmd = {"-y","-i", videoPath, "-i", waterMark.toString(), "-filter_complex", "overlay=5:5", "-c:v","libx264","-preset", "ultrafast", outputPath};
请有人向我解释如何在不增加输出大小的情况下提高 FFmpeg 水印速度。 谢谢
您提到 7MB 的视频需要 30-60 秒。
在速度和质量之间做出选择时总是需要权衡取舍。
我在 phone 上使用一个 7MB 的文件进行了测试,它花了 13 秒,仍然很慢,但我们不能期望比这更好。
提高速度的方法:
- 降低帧率,使用
-r
命令 - 更改比特率,使用
-b:v
和-b:a
命令 - 使用
-crf
更改恒定速率因子。默认值为21
The range of the quantizer scale is 0-51: where 0 is lossless, 23 is default, and 51 is worst possible. A lower value is a higher quality and a subjectively sane range is 18-28. Consider 18 to be visually lossless or nearly so: it should look the same or nearly the same as the input but it isn't technically lossless.
这是我发现在大多数 android 设备上效果最好的:
String[] s = {"-i", VideoPath, "-i", ImagePath, "-filter_complex", "[0:v]pad=iw:if(lte(ih\,iw)\,ih\,2*trunc(iw*16/9/2)):(ow-iw)/2:(oh-ih)/2[v0];[1:v][v0]scale2ref[v1][v0];[v0][v1]overlay=x=(W-w)/2:y=(H-h)/2[v]", "-map", "[v]", "-map", "0:a", "-c:v", "libx264", "-preset", "ultrafast", "-r", myFrameRate, directoryToStore[0] + "/" + SavedVideoName};
我稍微降低了帧率,你可以尝试一下最适合你的。我正在使用 mp4parser
检索帧速率。
我必须感谢@Gyan,他为我提供了一种完美缩放放置在视频顶部的图像的方法,你可以看看我问的问题
如果您不确定帧率,您可以将其从命令中删除并首先测试您的速度是否降低。
试一试,有问题欢迎追问
OP 选择使用以下命令:
String[] cmd = {"-y","-i", videoPath, "-i", waterMark.toString(), "-filter_complex", "overlay=(main_w-overlay_w-10):5", "-map", "0:a","-c:v", "libx264", "-crf", "28","-preset", "ultrafast" ,outputPath};
编辑
只是添加我提到的命令并提供如何使用它等的详细说明:
String[] cmd = {"-i", videoPath, "-i", waterMark.toString(), "-filter_complex", "[0:v]pad=iw:if(lte(ih\,iw)\,ih\,2*trunc(iw*16/9/2)):(ow-iw)/2:(oh-ih)/2[v0];[1:v][v0]scale2ref[v1][v0];[v0][v1]overlay=x=(W-w)/2:y=(H-h)/2[v]", "-map", "[v]", "-map", "0:a", "-c:v", "libx264", "-preset", "ultrafast", "-r", myFrameRate, outputPath};
这适用于显示纵横比为 16:9
的设备。如果您希望此滤镜适用于所有设备,则必须获取设备的宽高比并分别更改滤镜 16/9/2
。
您可以通过创建此方法来获取设备纵横比:
int gcd(int p, int q) {
if (q == 0) return p;
else return gcd(q, p % q);
}
void ratio(int a, int b) {
final int gcd = gcd(a,b);
if(a > b) {
setAspectRatio(a/gcd, b/gcd);
} else {
setAspectRatio(b/gcd, a/gcd);
}
}
void setAspectRatio(int a, int b) {
System.out.println("aspect ratio = "+a + " " + b);
//This is the string that will be used in the filter (instead of hardcoding 16/9/2
filterAspectRatio = a + "/" + b + "/" + "2";
}
现在您有了正确的纵横比,您可以相应地更改滤镜。
接下来,创建一个水印并将其添加到视图中,使该视图大小与设备相同 (match_parent
),并且 scale/place 水印位于您希望的位置。然后您可以通过调用获取位图:
Bitmap waterMarkBitmap = watermarkView.getDrawingCache();
并从 Bitmap
创建一个文件,如下所示:
String outputFilename = "myCoolWatermark.png"; //provide a name for you saved watermark
File path = Environment.getExternalStorageDirectory(); //this can be changed to where you want to store the bitmap
File waterMark = new File(path, outputFilename);
try (FileOutputStream out = new FileOutputStream(waterMark)) {
waterMarkBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); // PNG is a lossless format, the compression factor (100) is ignored
} catch (IOException e) {
e.printStackTrace();
}
水印已创建并可以重复使用,或者您可以在完成后将其删除。
现在可以调用上面的命令了
这是一个很常见的问题。 简单的答案是,您无法在 Android 上提高 ffmpeg
的编码速度。 您正在 phone 上编码,所以您不要指望 desktop/server 使用编码器和不支持硬件加速的性能。
用户可以做几件事:
Stream copy 带有
-c:a copy
的音频(您已经这样做了)。用
-preset ultrafast
为了编码速度放弃编码效率(你也已经这样做了)使用 scale 过滤器使输出宽度 x 高度变小(可能不是您可以接受的选项)。
确保您的 x264 未使用
--disable-asm
编译,以便您可以利用 x264 中的各种 ARM 和 NEON 优化来显着提高编码速度。但是,我不知道哪些 Android 设备支持该功能,但值得研究。要快速检查您是否正在使用任何优化,请参考ffmpeg
的控制台输出并搜索using cpu capabilities
。如果none!
那么它没有使用任何优化,否则它可能会说ARMv7 NEON
或类似的东西。将编码卸载到服务器。也可以节省用户的电池寿命。
这一切都是为了一个恼人的水印?避免重新编码,使用播放器叠加水印。
显然FFmpeg has MediaCodec decoding support on Android,但编码是这里的瓶颈。但是也许它会节省几帧。
向 FFmpeg 发送补丁以启用 MediaCodec 编码支持,或者等待几年让其他人这样做。
忘记
ffmpeg
,直接使用MediaCodec。我对此一无所知,也懒得查找,但我认为它使用硬件进行编码,我猜你可以用它来制作覆盖图。如果我错了有人纠正我。