ffmpeg二進制地址:https://download.csdn.net/download/peng825223208/12207961
根據下面代碼可以測試三種case:
1:通過視頻地址,在線邊解碼,截取圖片信息
2:通過視頻地址,下載視頻到本地,使用本地視頻文件,截取圖片信息
3:通過視頻地址,下載視頻到本地,使用本地視頻文件,使用ss參數獲取視頻截幀文件。
三種case的測試結果爲:
780s視頻:5秒截取一幀
視頻地址:http://dl-shanghai.oss.yunpan.360.cn/oss/yvideo/dWZpUDduQ00zaVkubXA0?scid=207&fhash=f225917356e4ea8f0e551428699f0d716f2f029f&sz=126346680&e=1583723435&uid=323340911&validity=0&keyspace=1&s=3976267b36cf73b886c626d5bbb2f3e2
方案1:73163ms 截取出來158幀
方案2:先下載,再處理。新方案耗時:133671ms 下載時間:67160ms 處理時間:66511ms 截取出來156幀
方案3:先下載,ss再處理。新方案2耗時:68591ms 下載時間:67188ms 處理時間:1388ms 截取出來43幀
視頻地址:https://content.cc.heytapmobi.com/resource/interact/playurl302?source=qie&outId=RFjFgL4-b_Nak5jHVQNmW-FWpzk_1ECtx4LDq1fh0JSrtKtZgrkxSG3C8WceTjLQn1TvKQ89op-qEdfHKm56lF0J7nVhKwTzub1
209s視頻:5秒截取一幀
893
方案1:7293ms 截取出來44幀
方案2:先下載,再處理。 新方案1耗時:8724ms 下載時間:1587ms 處理時間:7136ms 截取出來43幀
方案3:先下載,ss再處理。新方案2耗時:2103ms 下載時間:1871ms 處理時間:218ms 截取出來43幀
視頻地址:https://voppo1.go2yd.com/user_upload/15829702606628cb8e1dc708bec91f7320db323803fdc.mp4_bd.mp4
205s視頻:5秒截取一幀
893
方案1:3779ms 截取出來43幀
方案2:先下載,再處理。新方案耗時:4899ms 下載時間:1212ms 處理時間:3686ms 截取出來43幀
方案3:先下載,ss再處理。新方案2耗時:637ms 下載時間:377ms 處理時間:205ms 截取出來43幀
結論:第三種情況處理速度會有提升
package main
import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"path/filepath"
"context"
"syscall"
"regexp"
"os/exec"
"strconv"
"bytes"
)
type HttpClient struct {
Client http.Client
AddrIp string
}
func NewHttpClientImage(connTimeout time.Duration, readTimeout time.Duration, newhttpclient *HttpClient) {
client := http.Client{
Transport: &http.Transport{
Dial: func(netw, addr string) (net.Conn, error) {
c, err := net.DialTimeout(netw, addr, connTimeout)
if err != nil {
return nil, err
}
newhttpclient.AddrIp = c.RemoteAddr().String()
c.SetDeadline(time.Now().Add(readTimeout))
return c, nil
},
},
}
newhttpclient.Client = client
}
func (this *HttpClient) Get(httpUrl string, postParams map[string]string, referer bool) ([]byte, error) {
u, err := url.Parse(httpUrl)
if err != nil {
return nil, err
}
q := u.Query()
for key, value := range postParams {
q.Set(key, value)
}
u.RawQuery = q.Encode()
url := ""
if postParams != nil {
url = u.String()
} else {
url = httpUrl
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("user-agent", "shumei")
if referer {
req.Header.Set("referer", "https://www.fengkongcloud.com")//vipkid使用
}
resp, reqErr := this.Client.Do(req)
if reqErr != nil {
return nil, reqErr
}
defer resp.Body.Close()
var Codeerr error
switch resp.StatusCode {
case 200:
break
default:
Codeerr = errors.New(fmt.Sprintf("Server get request error,statuscode: %v",resp.StatusCode))
break
}
if Codeerr != nil {
return nil,Codeerr
}
data, respErr := ioutil.ReadAll(resp.Body)
if respErr != nil {
return nil, respErr
}
return data, nil
}
func (this *HttpClient) Post(httpUrl string, headers map[string]string, body string) (string, error) {
req, _ := http.NewRequest("POST", httpUrl, strings.NewReader(body))
for key, value := range headers {
req.Header.Set(key, value)
}
resp, reqErr := this.Client.Do(req)
if reqErr != nil {
return "", reqErr
}
defer resp.Body.Close()
data, respErr := ioutil.ReadAll(resp.Body)
if respErr != nil {
return "", respErr
}
return string(data), nil
}
//README:在可執行文件當前文件夾下建立images文件夾用於保存截取出來的圖片
//參數1:圖片地址
func main() {
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
fmt.Println("dir:", dir)
var ffmpegPath string = dir + "/ffmpeg"
urlpath:=os.Args[1]
savefilename := dir+"/video.mp4"
st := time.Now().UnixNano()
//var freq float64 = 60
//根據視頻地址,下載視頻到本地
if true {
client := HttpClient{}
ConnectTimeout := time.Duration(1000)*time.Nanosecond*1e9
ReadTimeout := time.Duration(1000)*time.Nanosecond*1e9
NewHttpClientImage(ConnectTimeout, ReadTimeout, &client)
imgbytes, err := client.Get(urlpath, nil, false)
urlpath = savefilename
ioutil.WriteFile(savefilename, imgbytes, 0777)
fmt.Println("savefilename:", savefilename)
fmt.Println("download err:", err)
}
cost := float64((time.Now().UnixNano()-st)/1000000)
fmt.Println("download timecost:", cost)
if true {
path := "./images/"
if err := os.MkdirAll(path, os.ModePerm); err != nil {
fmt.Println("mkdir failed!!!")
}
//ffmpeg使用命令: ffmpeg -i http://video.pearvideo.com/head/20180301/cont-1288289-11630613.mp4 -r 1 -t 4 -f image2 image-%05d.jpeg
/*
-t 代表持續時間,單位爲秒
-f 指定保存圖片使用的格式,可忽略。
-r 指定抽取的幀率,即從視頻中每秒鐘抽取圖片的數量。1代表每秒抽取一幀。
-ss 指定起始時間
-vframes 指定抽取的幀數
*/
videoLen, _ := GenerateLength(ffmpegPath, urlpath, "1111111111")
testFfmpegParams(urlpath, path, ffmpegPath, 60, videoLen)
//invokeFfmpeg(urlpath, path, ffmpegPath, freq)
//getLastFrame(urlpath, path, ffmpegPath)
}
cost = float64((time.Now().UnixNano()-st)/1000000)
fmt.Println("download and process timecost:", cost)
}
//通過-ss參數 獲取視頻中的圖片幀
func testFfmpegParams(url string, path string, ffmpegPath string, freq int, videoLen int) string {
st := time.Now().UnixNano()
var outputerror string
for i:=0; i<videoLen; i=i+freq {
sec := strconv.Itoa(i)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50000)*time.Millisecond)
cmd := exec.CommandContext(ctx, ffmpegPath,
"-loglevel", "error",
"-y",
"-ss", sec,
"-t", "1",
"-i", url,
"-vframes","1",
path+"/"+ sec +".jpg")
defer cancel()
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
outputerror += fmt.Sprintf("lastframecmderr:%v;", err)
}
if stderr.Len() != 0 {
outputerror += fmt.Sprintf("lastframestderr:%v;", stderr.String())
}
if ctx.Err() != nil {
outputerror += fmt.Sprintf("lastframectxerr:%v;", ctx.Err())
}
}
cost := float64((time.Now().UnixNano()-st)/1000000)
fmt.Println("jiezhencost:", cost)
return outputerror
}
//獲取視頻時長,通過正則解析日誌獲取
func GenerateLength(ffmpegPath string, url string, reqId string) (int, error) {
st := time.Now().UnixNano()
var length int
for i := 0; i < 2; i++ {
//ctx, cancel := context.WithTimeout(context.Background(), time.Duration(config.Conf.ConfigMap.VideoC.CalcLengthTimeout)*time.Millisecond)
//視頻處理使用,延長超時時間
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
cmd := exec.CommandContext(ctx, ffmpegPath,
"-i", url)
defer cancel()
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// always return err exit 1; do not catch
cmd.Run()
str := video_length_regexp.FindString(stderr.String())
str = "2006-01-02" + strings.TrimPrefix(str, "Duration:")
if videotime, err := time.Parse("2006-01-02 15:04:05", str); err != nil {
if ctx.Err() != nil {
fmt.Println("GenerateLength Err:", ctx.Err())
}
length = 0
} else {
length = videotime.Hour()*3600 + videotime.Minute()*60 + videotime.Second()
break
}
}
cost := float64((time.Now().UnixNano()-st)/1000000)
fmt.Println("videolengthcost:", cost)
fmt.Println("---------->>>videolength:", length)
return length, nil
}
//獲取視頻中最後一幀的圖片
func getLastFrame(url string, path string, ffmpegPath string) string {
fmt.Println("url:", url)
fmt.Println("ffmpeg:", ffmpegPath)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50000)*time.Millisecond)
cmd := exec.CommandContext(ctx, ffmpegPath,
"-timeout", "60000000",
"-loglevel", "error",
"-y",
"-ss", "13",
"-t", "1",
"-i", url,
"-vframes","1",
path+"/"+"lastfram.jpg")
defer cancel()
var stderr bytes.Buffer
cmd.Stderr = &stderr
var outputerror string
fmt.Println("lastframpath:", path+"/"+"lastfram.jpg")
err := cmd.Run()
fmt.Println("zuihouyzihenerr:", err)
if err != nil {
outputerror += fmt.Sprintf("lastframecmderr:%v;", err)
}
if stderr.Len() != 0 {
outputerror += fmt.Sprintf("lastframestderr:%v;", stderr.String())
}
if ctx.Err() != nil {
outputerror += fmt.Sprintf("lastframectxerr:%v;", ctx.Err())
}
fmt.Println("outputerror:", outputerror)
return outputerror
}
//通過幀率獲取視頻中的圖片和testFfmpegParams函數一樣功能.
func invokeFfmpeg(urlpath string, path string, ffmpegPath string, Freq float64) {
fmt.Println("urlpath:", urlpath)
currentTime := time.Now().UnixNano()
//ffmpeg -i 'http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8' -r 1 -t 200 -f image2 images/image-%05d.jpeg //中央電視臺視頻流中的圖片
//ffmpeg -i 'http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8' -r 10 -vcodec copy video/aaaaa.mp4 //copy中央電視臺的視頻流中的視頻
//var Freq float64 = 5 //設置多少秒截一幀
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(14400000)*time.Millisecond)
//cmd := exec.CommandContext(ctx, ffmpegPath,
// "-i", urlpath,
// "-vcodec", "copy",
// path+"/"+"tttt.mp4")
cmd := exec.CommandContext(ctx, ffmpegPath,
"-loglevel", "error",
"-i", urlpath,
"-f", "image2",
"-r", strconv.FormatFloat(float64(1/Freq), 'e', -1, 64),
path+"/" + "%04d.jpg")
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
defer cancel()
var stderr bytes.Buffer
cmd.Stderr = &stderr
var outputerror string
err := cmd.Run()
if err != nil {
outputerror += fmt.Sprintf("cmderr:%v;", err)
}
if stderr.Len() != 0 {
outputerror += fmt.Sprintf("stderr:%v;", stderr.String())
}
if ctx.Err() != nil {
outputerror += fmt.Sprintf("ctxerr:%v;", ctx.Err())
}
cost := float64((time.Now().UnixNano()-currentTime)/1000000)
fmt.Println("invokeFfmpeg err:", outputerror)
fmt.Println("invokeFfmpeg videolengthcost:", cost)
}