ffmpeg 多画面mp4+混音

一、视频多画面 

@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
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值