一、视频多画面
@echo off
ffmpeg ^
-i 2021_04_12/20198002/ch01_20210412_150310_150500_001.mp4 ^
-i 2021_04_12/20198002/ch02_20210412_150310_150500_001.mp4 ^
-filter_complex ^
[0]pad=2*iw[a];[a][1:v]overlay=w ^
outputtest2.mp4 -y
Pause
注释:
[0]pad=2*iw[a] >> [0]代表第一个输入视频源 pad扩展画面(不是拉伸,扩展在原有画面外补黑)
>> 2*iw 格式w:h 代表扩展后画面宽高
>> [a] 输入到a容器
把第一个输入视频画面宽度扩展2被,输出到容器a中
[a]:[1:v]overlay=w >> [1:v] 表示取第二个输入视频的video数据(audio数据为a)
>> =w 格式为x:y 代表画面位置 从左上角开始
>> overlay 把[1:v] 数据放置在容器a的起始点为(w,0)的位置
二、混音
@echo off
ffmpeg ^
-i 2021_04_12/20198002/ch01_20210412_150310_150500_001.mp4 ^
-i 2021_04_12/20198002/ch02_20210412_150310_150500_001.mp4 ^
-filter_complex ^
[0:v]pad=2*iw[a];[a][1:v]overlay=w[vout];^
[0:a][1:a]amix=inputs=2:duration=first[aout] ^
-map [vout] -map [aout] ^
outputtest_amix2.mp4 -y
Pause
@echo off
ffmpeg ^
-i ch01_20210412_150310_150500_001.mp4 ^
-i ch02_20210412_150310_150500_001.mp4 ^
-i ch03_20210412_150310_150500_001.mp4 ^
-i ch04_20210412_150310_150500_001.mp4 ^
-filter_complex ^
[0:v]pad=2*iw:2*ih[a];[a][1:v]overlay=w[b];[b][2:v]overlay=0:h[c];[c][3:v]overlay=w:h[vout];^
[0:a][1:a][2:a][3:a]amix=inputs=4:duration=first[aout] ^
-map [vout] -map [aout] ^
outputtest4.mp4 -y
Pause
注释
w[out] 把filter的结果放置到vout容器中
-map 输出到文件顺序
效果:
ffmpeg g726 pcm:
ffmpeg -y -f g726le -code_size 5 -i ff.g726 -ar 8000 -ac 1 -f wav ff.wav
package main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
)
// type ffmp4info struct {
// Streams []struct {
// CodecName string `json:"codec_name"`
// Width int `json:"width"`
// Height int `json:"height"`
// } `json:"streams"`
// }
type complexs struct {
Width int
Height int
HasAudio bool
}
func parseMp4(filename string) complexs {
// cmdArgs := []string{"-show_entries", "format=duration,size,bit_rate,filename", "-show_streams", "-v", "quiet", "-of", "csv=\"p=0\"", "-of", "json", "-i", filename}
// cmd := exec.Command("ffprobe", cmdArgs...)
// var out bytes.Buffer
// cmd.Stdout = &out
// if err := cmd.Run(); err != nil {
// return nil, err
// }
// // fmt.Printf("%s\n", out.String())
// var info ffmp4info
// if err := json.Unmarshal(out.Bytes(), &info); err != nil {
// return nil, err
// }
// return &info, nil
var info complexs
cmd := exec.Command("ffmpeg", "-i", filename)
out, _ := cmd.CombinedOutput()
// fmt.Printf("%s\n", str)
pos := bytes.Index(out, []byte("Stream #0:"))
arr := bytes.Split(out[pos:], []byte(","))
for _, v := range arr {
if bytes.Contains(v, []byte(")")) || !bytes.Contains(v, []byte("x")) {
continue
}
fmt.Printf("%s\n", string(v))
fmt.Sscanf(string(v), "%dx%d", &info.Width, &info.Height)
break
}
if bytes.Count(out, []byte("Stream #0:")) > 1 {
info.HasAudio = true
}
fmt.Println(info)
return info
}
var (
maxWidth int = 0
maxHeight int = 0
)
func main() {
argLen := len(os.Args)
if argLen < 4 {
log.Fatalln("input error")
}
files := os.Args[1 : argLen-1]
cmdArgs := []string{}
var cs []complexs
for _, f := range files {
info := parseMp4(f)
if info.Width > maxWidth {
maxWidth = info.Width
}
if info.Height > maxHeight {
maxHeight = info.Height
}
cs = append(cs, info)
cmdArgs = append(cmdArgs, "-i")
cmdArgs = append(cmdArgs, f)
}
cmdArgs = append(cmdArgs, "-filter_complex")
filenum := len(cs)
var (
plexstr string
amixchn int
)
switch filenum {
case 2:
plexstr, amixchn = complex2video(cs)
case 3:
plexstr, amixchn = complex4video(cs, filenum)
case 4:
plexstr, amixchn = complex4video(cs, filenum)
case 5:
plexstr, amixchn = complex6video(cs, filenum)
case 6:
plexstr, amixchn = complex6video(cs, filenum)
case 7:
plexstr, amixchn = complex9video(cs, filenum)
case 8:
plexstr, amixchn = complex9video(cs, filenum)
case 9:
plexstr, amixchn = complex9video(cs, filenum)
}
if amixchn > 0 {
str := fmt.Sprintf("%samix=inputs=%d:duration=first[aout]", plexstr, amixchn)
cmdArgs = append(cmdArgs, str)
}
cmdArgs = append(cmdArgs, "-map")
cmdArgs = append(cmdArgs, "[vout]")
if amixchn > 0 {
cmdArgs = append(cmdArgs, "-map")
cmdArgs = append(cmdArgs, "[aout]")
}
cmdArgs = append(cmdArgs, os.Args[argLen-1])
cmdArgs = append(cmdArgs, "-y")
fmt.Println(cmdArgs)
exec.Command("ffmpeg", cmdArgs...).Run()
}
func complex2video(cs []complexs) (string, int) {
str := fmt.Sprintf("pad=%d:%d:%d:%d:black[a];[a][1:v]overlay=%d:%d[vout];", 2*maxWidth, maxHeight,
(maxWidth-cs[0].Width)/2, (maxHeight-cs[0].Height)/2, maxWidth+(maxWidth-cs[1].Width)/2, (maxHeight-cs[1].Height)/2)
amixchn := 0
if cs[0].HasAudio {
str += "[0:a]"
amixchn++
}
if cs[1].HasAudio {
str += "[1:a]"
amixchn++
}
return str, amixchn
}
func complex4video(cs []complexs, size int) (string, int) {
str := fmt.Sprintf("pad=%d:%d:%d:%d:black[a];[a][1:v]overlay=%d:%d[b];[b][2:v]overlay=%d:%d", 2*maxWidth, 2*maxHeight,
(maxWidth-cs[0].Width)/2, (maxHeight-cs[0].Height)/2, maxWidth+(maxWidth-cs[1].Width)/2, (maxHeight-cs[1].Height)/2,
(maxWidth-cs[2].Width)/2, maxHeight+(maxHeight-cs[2].Height)/2)
if size == 4 {
tstr := fmt.Sprintf("[c];[c][3:v]overlay=%d:%d", maxWidth+(maxWidth-cs[3].Width)/2, maxHeight+(maxHeight-cs[3].Height)/2)
str += tstr
}
str += "[vout];"
amixchn := 0
if cs[0].HasAudio {
str += "[0:a]"
amixchn++
}
if cs[1].HasAudio {
str += "[1:a]"
amixchn++
}
if cs[2].HasAudio {
str += "[2:a]"
amixchn++
}
if size == 4 && cs[3].HasAudio {
str += "[3:a]"
amixchn++
}
return str, amixchn
}
func complex6video(cs []complexs, size int) (string, int) {
str := fmt.Sprintf("scale=%d:%d,pad=%d:%d:%d:%d:black[a];[a][1:v]overlay=%d:%d[b];[b][2:v]overlay=%d:%d[c];[c][3:v]overlay=%d:%d[d];[d][4:v]overlay=%d:%d", 2*cs[0].Width, 2*cs[0].Height, 3*maxWidth, 3*maxHeight,
(maxWidth - cs[0].Width), (maxHeight - cs[0].Height), // 第1个位置
2*maxWidth+(maxWidth-cs[1].Width)/2, (maxHeight-cs[1].Height)/2, // 第2个位置
2*maxWidth+(maxWidth-cs[2].Width)/2, maxHeight+(maxHeight-cs[2].Height)/2, // 第3个位置
(maxWidth-cs[3].Width)/2, 2*maxHeight+(maxHeight-cs[3].Height)/2, // 第4个位置
maxWidth+(maxWidth-cs[4].Width)/2, 2*maxHeight+(maxHeight-cs[4].Height)/2, // 第5个位置
)
if size == 6 {
tstr := fmt.Sprintf("[e];[e][5:v]overlay=%d:%d", 2*maxWidth+(maxWidth-cs[5].Width)/2, 2*maxHeight+(maxHeight-cs[5].Height)/2)
str += tstr
}
str += "[vout];"
amixchn := 0
if cs[0].HasAudio {
str += "[0:a]"
amixchn++
}
if cs[1].HasAudio {
str += "[1:a]"
amixchn++
}
if cs[2].HasAudio {
str += "[2:a]"
amixchn++
}
if cs[3].HasAudio {
str += "[3:a]"
amixchn++
}
if cs[4].HasAudio {
str += "[4:a]"
amixchn++
}
if size == 6 && cs[5].HasAudio {
str += "[5:a]"
amixchn++
}
return str, amixchn
}
func complex9video(cs []complexs, size int) (string, int) {
str := fmt.Sprintf("pad=%d:%d:%d:%d:black[a];[a][1:v]overlay=%d:%d[b];[b][2:v]overlay=%d:%d[c];[c][3:v]overlay=%d:%d[d];[d][4:v]overlay=%d:%d[e];[e][5:v]overlay=%d:%d[f];[f][6:v]overlay=%d:%d", 3*maxWidth, 3*maxHeight,
(maxWidth-cs[0].Width)/2, (maxHeight-cs[0].Height)/2, // 第1个位置
maxWidth+(maxWidth-cs[1].Width)/2, (maxHeight-cs[1].Height)/2, // 第2个位置
2*maxWidth+(maxWidth-cs[2].Width)/2, (maxHeight-cs[2].Height)/2, // 第3个位置
(maxWidth-cs[3].Width)/2, maxHeight+(maxHeight-cs[3].Height)/2, // 第4个位置
maxWidth+(maxWidth-cs[4].Width)/2, maxHeight+(maxHeight-cs[4].Height)/2, // 第5个位置
2*maxWidth+(maxWidth-cs[5].Width)/2, maxHeight+(maxHeight-cs[5].Height)/2, // 第6个位置
(maxWidth-cs[6].Width)/2, 2*maxHeight+(maxHeight-cs[6].Height)/2, // 第7个位置
)
if size >= 8 {
tstr := fmt.Sprintf("[g];[g][7:v]overlay=%d:%d", maxWidth+(maxWidth-cs[7].Width)/2, 2*maxHeight+(maxHeight-cs[7].Height)/2)
str += tstr
}
if size >= 9 {
tstr := fmt.Sprintf("[h];[h][8:v]overlay=%d:%d", 2*maxWidth+(maxWidth-cs[8].Width)/2, 2*maxHeight+(maxHeight-cs[8].Height)/2)
str += tstr
}
str += "[vout];"
amixchn := 0
if cs[0].HasAudio {
str += "[0:a]"
amixchn++
}
if cs[1].HasAudio {
str += "[1:a]"
amixchn++
}
if cs[2].HasAudio {
str += "[2:a]"
amixchn++
}
if cs[3].HasAudio {
str += "[3:a]"
amixchn++
}
if cs[4].HasAudio {
str += "[4:a]"
amixchn++
}
if cs[5].HasAudio {
str += "[5:a]"
amixchn++
}
if cs[6].HasAudio {
str += "[6:a]"
amixchn++
}
if size >= 8 && cs[7].HasAudio {
str += "[7:a]"
amixchn++
}
if size >= 9 && cs[8].HasAudio {
str += "[8:a]"
amixchn++
}
return str, amixchn
}