golang調用ffmpeg根據幀率截取視頻中的圖片和調用ffmpeg獲取視頻時長

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

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章