一、基本知識(shí)
1.1 FFmpeg相關(guān)
FFmpeg 是領(lǐng)先的多媒體框架,能夠解碼、編碼、轉(zhuǎn)碼、混合、解密、流媒體、過(guò)濾和播放人類(lèi)和機(jī)器創(chuàng)造的幾乎所有東西。它支持最晦澀的古老格式,直到最尖端的格式。無(wú)論它們是由某個(gè)標(biāo)準(zhǔn)委員會(huì)、社區(qū)還是公司設(shè)計(jì)的。它還具有高度的便攜性。
FFmpeg 可以在 Linux、Mac OS X、Microsoft Windows、BSDs、Solaris 等各種構(gòu)建環(huán)境、機(jī)器架構(gòu)和配置下編譯、運(yùn)行,并通過(guò)測(cè)試基礎(chǔ)設(shè)施 FATE
它包含了 libavcodec、libavutil、libavformat、libavfilter、libavdevice、libswscale 和 libswresample,可以被應(yīng)用程序使用。還有 ffmpeg、ffplay 和 ffprobe,可以被終端用戶(hù)用于轉(zhuǎn)碼和播放。
FFmpeg源碼下載地址:FFmpeg官網(wǎng)
(可以選擇下載源碼自己編譯并加入如x264, fdk-acc等,也可以直接下載動(dòng)/靜態(tài)庫(kù))
具體FFmpeg在Centos環(huán)境下編譯可以參考:FFmpeg在Centos環(huán)境下編譯
1.2 H.264相關(guān)
H.264是一種視頻編碼格式
視頻編碼是指視頻中存在很多冗余信息,比如圖像相鄰像素之間有較強(qiáng)的相關(guān)性,視頻序列的相鄰圖像之間內(nèi)容相似,人的視覺(jué)系統(tǒng)對(duì)某些細(xì)節(jié)不敏感等,對(duì)這部分冗余信息進(jìn)行處理的過(guò)程
常見(jiàn)的視頻編碼格式有:H.264是新一代的編碼標(biāo)準(zhǔn),以高壓縮高質(zhì)量和支持多種網(wǎng)絡(luò)的流媒體傳輸著稱(chēng)
1.3 YUV相關(guān)
在轉(zhuǎn)碼過(guò)程中需要將視頻解碼成yuv再重新編碼以便更改一些參數(shù), 也需要在yuv上做一些處理比如添加水印, 提升亮度,等等
YUV是一種視頻格式, YUV與RGB一樣,都是像素?cái)?shù)據(jù)的編碼格式,一組YUV渲染屏幕上的一個(gè)像素,控制屏幕用色彩的形式將事物表現(xiàn)出來(lái),其中Y表示像素中的亮度,英文是Luminance,U表示色度,英文是Chrominance,V表示濃度或飽和度,英文是Chroma。這是一種壓縮后的顏色表示方法,占用更少的物理空間,且對(duì)顏色的表現(xiàn)失真不明顯
YUV存儲(chǔ)方式有兩大分類(lèi):
-
Packed
從字面意思來(lái)看,packed是打包的意思,打包就不一定是平整的了,對(duì)應(yīng)到存儲(chǔ)方式上就是把YUV三種分量交叉存儲(chǔ),以YUY2為例,存儲(chǔ)方式為:Y0U0Y1V0 Y2U1Y3V1,這種方式在解析時(shí)就會(huì)比較麻煩 -
Planar
從字面意思上來(lái)看,planar是平面的意思,平面比較平整,對(duì)應(yīng)到存儲(chǔ)方式上就是把YUV三種分量分別存儲(chǔ),以I420為例,存儲(chǔ)方式為:YYYYYYYYUUVV,簡(jiǎn)單明了,先把Y存完,再存U,再存V,這種在解析時(shí)很方便
主流的YUV采樣方式:
- 4:4:4,如果要完全存儲(chǔ),那一個(gè)一個(gè)像素點(diǎn)就要存儲(chǔ)YUV三個(gè)分量,這種形式就是4:4:4
- 4:2:2,因?yàn)槿说难劬?duì)色度和飽和度不是特別敏感,所以一定程度上丟失一部分UV并不影響我們分辨顏色在存儲(chǔ)時(shí)就故意丟掉部分UV分量,用兩個(gè)Y分量共用一組UV分量,這種形式就是4:2:2
- 4:2:0或用四個(gè)Y分量共用一組UV,這種形式就是4:2:0
下圖中以黑點(diǎn)表示采樣該像素點(diǎn)的Y分量,以空心圓圈表示采用該像素點(diǎn)的UV分量在存儲(chǔ)時(shí)YUV各占一個(gè)字節(jié)Byte,如果4:4:4方式,那一個(gè)256X256分辨率的圖片要占用256×256×3=196608Byte,4:2:2方式要占用256×256×2=131072Byte,4:2:0方式要占用256×256×2/3=43690.7Byte,可以看到采用4:2:0方式存儲(chǔ)空間整整減少了一半
二、H264編碼原理
H264編碼過(guò)程主要分為五個(gè)模塊:
2.1 幀類(lèi)型分析
對(duì)輸入進(jìn)來(lái)的YUV數(shù)據(jù)的每一幀確定一個(gè)類(lèi)型,即I幀,P幀和B幀, I幀是內(nèi)部編碼幀,P幀是向前預(yù)測(cè)幀,B幀是雙向內(nèi)插幀。I幀不會(huì)依賴(lài)其他幀的信息,也就是自我進(jìn)行參考的幀。P和B幀的話(huà),都是會(huì)依賴(lài)其他幀信息來(lái)完成自身預(yù)測(cè)的幀,區(qū)別在于顯示序列中P幀是前向參考,B幀是前后雙向參考。
I 幀可以理解為電影中的一個(gè)完整畫(huà)面,里面包含了所有的圖像信息,而P幀和B幀記錄的是相對(duì)于I幀的變化
可以想象現(xiàn)在有一段視頻,一個(gè)人從畫(huà)面左邊走到右邊,剛打開(kāi)這個(gè)視頻的時(shí)候,顯示的第一幀圖像肯定是要自我重建的,因?yàn)闆](méi)有圖像可以參考,這樣的幀就是I幀。后面的再顯示第二張圖像發(fā)現(xiàn)除了畫(huà)面中除了人運(yùn)動(dòng)的一點(diǎn)點(diǎn)位置發(fā)生了變化,剩下靜止不動(dòng)的地方都和前一幀一樣。這樣的話(huà),就可以把前一幀靜止的數(shù)據(jù)直接復(fù)制過(guò)來(lái),當(dāng)前幀只需要把和前一幀的不同點(diǎn)(也就是運(yùn)動(dòng)位移矢量)保存下來(lái)就行,這樣的幀就是P/B幀,B幀因?yàn)檫€有后向參考,也就是說(shuō),它比P幀參考搜索的范圍更大,所以B幀的壓縮率相對(duì)更高。這時(shí)候鏡頭突然一轉(zhuǎn),給了這個(gè)人的臉一張?zhí)貙?xiě)。那么這時(shí)候就會(huì)需要重建一個(gè)新的畫(huà)面,就是一個(gè)新的 I 幀
2.2 幀內(nèi)/幀間預(yù)測(cè)
通常,編碼器會(huì)通過(guò)算法將圖像劃分為一塊一塊的,然后逐塊進(jìn)行后續(xù)的壓縮處理
假設(shè)當(dāng)前的塊不在圖像邊緣,我們可以用上方相鄰塊邊界鄰近值作為基礎(chǔ)值,也就是上面一行中的每一個(gè)值,都垂直向下做拷貝,構(gòu)建出和源 YUV 塊一樣大小的預(yù)測(cè)塊,這種構(gòu)建預(yù)測(cè)塊的方式,叫做垂直預(yù)測(cè)模式,屬于幀內(nèi)預(yù)測(cè)模式的一種。與它相似的,還有水平預(yù)測(cè)模式、均值預(yù)測(cè)模式(也就是4x4的均值填充整個(gè) 4x4)等
緊接著,用源YUV的數(shù)據(jù)和預(yù)測(cè)YUV的數(shù)據(jù)做差值,得到殘差塊,這樣我們?cè)诖a流中,就直接傳輸殘差的數(shù)據(jù)和當(dāng)前4x4塊的預(yù)測(cè)模式的標(biāo)志位就行,這極大地節(jié)省了碼流
2.3 變換+量化
預(yù)測(cè)之后的殘差經(jīng)過(guò)DCT空頻變換,直流和低頻(相對(duì)平坦,圖像或塊中大部分占比)能量集中在左上,高頻(細(xì)節(jié),圖像或塊中少部分占比)能量集中在右下,DCT本身雖然沒(méi)有壓縮作用,卻為以后壓縮時(shí)的取舍,奠定了必不可少的基礎(chǔ)
于人眼對(duì)高頻信號(hào)不敏感,我們可以定義這樣一個(gè)變量QP=5,將變換塊中所有的值都除以QP,這樣做進(jìn)一步節(jié)省傳輸碼流位寬,同時(shí)主要去掉了高頻分量的值,在解碼端只需要將變換塊中所有的值在乘QP就可以基本還原低頻分量
我們將QP運(yùn)算的過(guò)程稱(chēng)為量化,可見(jiàn)量化值越大,丟掉的高頻信息就越多,再加上編碼器中都是用整形變量代表像素值,所以量化值最大還原的低頻信息也會(huì)越不準(zhǔn)確,即造成的失真就越大,塊效應(yīng)也會(huì)越大,視頻編碼的質(zhì)量損失主要來(lái)源于此
2.4 濾波
當(dāng)量化值波動(dòng)特別大的時(shí)候,可能會(huì)造成畫(huà)面真實(shí)邊界的區(qū)域內(nèi)有明顯的塊效應(yīng),濾波是一個(gè)減少塊效應(yīng)提升畫(huà)面質(zhì)量的操作。
主要有三部分操作:1、初步估算塊效應(yīng)邊界強(qiáng)度;2、區(qū)分真假邊界 ;3、計(jì)算差值
2.5 熵編碼
在真實(shí)網(wǎng)絡(luò)傳輸?shù)倪^(guò)程中肯定都是二進(jìn)制碼,所以需要將當(dāng)前的像素值進(jìn)一步壓縮成二進(jìn)制流。在編碼中一共有兩種熵編碼方式:
- 較為簡(jiǎn)單的Cavlc
- 壓縮效率更高但運(yùn)算更復(fù)雜的Cabac
具體參考論文《H.264中CABAC算法與CAVLC算法比較和改進(jìn)》
三、H264解碼為YUV
FFmpeg的源代碼和庫(kù)都是C的代碼和庫(kù),golang語(yǔ)言使用FFmpeg的接口需要使用cgo對(duì)C庫(kù)的接口進(jìn)行封裝,以便go代碼調(diào)用,這里使用的是go的第三方庫(kù)
import (
"github.com/giorgisio/goav/avcodec"
"github.com/giorgisio/goav/avformat"
"github.com/giorgisio/goav/avutil"
... ... ... ... ...
)
3.1 代碼邏輯及使用API
H264解碼為YUV,整個(gè)代碼邏輯如下:
- 創(chuàng)建AvformatContext結(jié)構(gòu)體:
func avformat.AvformatAllocContext() *avformat.Context
- 打開(kāi)輸入文件:
func avformat.AvformatOpenInput(ps **avformat.Context, fi string, fmt *avformat.InputFormat, d **avutil.Dictionary) int
- 獲取輸入文件的視頻流信息:
func (*avformat.Context).AvformatFindStreamInfo(d **avutil.Dictionary) int
- 循環(huán)查找視頻中包含的流信息,直到找到視頻類(lèi)型的流:
func (*avformat.Context).Streams() []*avformat.Stream
func (*avformat.Stream).CodecParameters() *avcodec.AvCodecParameters
func (*avcodec.AvCodecParameters).AvCodecGetType() avcodec.MediaType
- 查找解碼器:
func avcodec.AvcodecFindDecoder(id avcodec.CodecId) *avcodec.Codec or
func avcodec.AvcodecFindDecoderByName(name string) *avcodec.Codec
- 配置解碼器:
func (*avcodec.Codec).AvcodecAllocContext3() *avcodec.Context
func (*avcodec.Context).AvcodecCopyContext(ctxt2 *avcodec.Context) int
- 打開(kāi)解碼器:
func (*avcodec.Context).AvcodecOpen2(c *avcodec.Codec, d **avcodec.Dictionary) int
- 分配frame和packet結(jié)構(gòu)
func avutil.AvFrameAlloc() *avutil.Frame
func avcodec.AvPacketAlloc() *avcodec.Packet
- 提供packet數(shù)據(jù)作為解碼器的輸入,frame接收解碼器的輸出
func (*avformat.Context).AvReadFrame(pkt *avcodec.Packet)
func (*avcodec.Context).AvcodecSendPacket(packet *avcodec.Packet) int
func (*avcodec.Context).AvcodecReceiveFrame(frame *avcodec.Frame) int
3.2 具體代碼實(shí)現(xiàn)
package main
import (
"errors"
"fmt"
"os"
"unsafe"
"github.com/giorgisio/goav/avcodec"
"github.com/giorgisio/goav/avformat"
"github.com/giorgisio/goav/avutil"
)
var width int
var height int
func FFmpeg_H264DecodeToYUV(input_filename string, output_filename string) error {
file, _ := os.Create(output_filename)
//創(chuàng)建AvformatContext結(jié)構(gòu)體
Inputformatctx := avformat.AvformatAllocContext()
//打開(kāi)文件
if avformat.AvformatOpenInput(&Inputformatctx, input_filename, nil, nil) != 0 {
return errors.New("Unable to open input file " + input_filename)
}
//獲取視頻流信息
if Inputformatctx.AvformatFindStreamInfo(nil) < 0 {
Inputformatctx.AvformatCloseInput()
return errors.New("Error: Couldn't find stream information.")
}
Inputformatctx.AvDumpFormat(0, input_filename, 0)
nCount := 0
//循環(huán)查找視頻中包含的流信息,直到找到視頻類(lèi)型的流
//記錄下來(lái),保存到videoStreamIndex變量中
var i int
for i = 0; i < int(Inputformatctx.NbStreams()); i++ {
switch Inputformatctx.Streams()[i].CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
// Get a pointer to the codec context for the video stream
pCodecCtxOrig := Inputformatctx.Streams()[i].Codec()
// 查找解碼器
pCodec := avcodec.AvcodecFindDecoder(avcodec.CodecId(pCodecCtxOrig.GetCodecId()))
//pCodec := avcodec.AvcodecFindDecoderByName("libx264")
if pCodec == nil {
return errors.New("Unsupported codec!----------")
}
// 配置解碼器
pCodecCtx := pCodec.AvcodecAllocContext3()
if pCodecCtx.AvcodecCopyContext((*avcodec.Context)(unsafe.Pointer(pCodecCtxOrig))) != 0 {
return errors.New("Couldn't copy codec context--------------")
}
// 打開(kāi)解碼器
if pCodecCtx.AvcodecOpen2(pCodec, nil) < 0 {
return errors.New("Could not open codec-------------")
}
width := pCodecCtx.Width()
height := pCodecCtx.Height()
pFrameYUV := avutil.AvFrameAlloc()
packet := avcodec.AvPacketAlloc() //分配一個(gè)packet
packet.AvNewPacket(int(width * height)) //調(diào)整packet的數(shù)據(jù)
fmt.Println("width:", width)
fmt.Println("height:", height)
for Inputformatctx.AvReadFrame(packet) >= 0 {
// Is this a packet from the video stream?
if packet.StreamIndex() == i {
// 提供原始數(shù)據(jù)包數(shù)據(jù)作為解碼器的輸入
if pCodecCtx.AvcodecSendPacket(packet) >= 0 {
//從解碼器返回解碼的輸出數(shù)據(jù)
for pCodecCtx.AvcodecReceiveFrame((*avcodec.Frame)(unsafe.Pointer(pFrameYUV))) == 0 {
nCount++
bytes := []byte{}
//y
ptr := uintptr(unsafe.Pointer(avutil.Data(pFrameYUV)[0]))
for j := 0; j < width*height; j++ {
bytes = append(bytes, *(*byte)(unsafe.Pointer(ptr)))
ptr++
}
//u
ptr = uintptr(unsafe.Pointer(avutil.Data(pFrameYUV)[1]))
for j := 0; j < width*height/4; j++ {
bytes = append(bytes, *(*byte)(unsafe.Pointer(ptr)))
ptr++
}
//v
ptr = uintptr(unsafe.Pointer(avutil.Data(pFrameYUV)[2]))
for j := 0; j < width*height/4; j++ {
bytes = append(bytes, *(*byte)(unsafe.Pointer(ptr)))
ptr++
}
//寫(xiě)文件
file.Write(bytes)
}
if nCount == 100 {
break
}
}
}
packet.AvPacketUnref()
}
fmt.Printf("There are %d frames int total.\n", nCount)
// Free the YUV frame
avutil.AvFrameFree(pFrameYUV)
packet.AvFreePacket()
file.Close()
// Close the codecs
pCodecCtx.AvcodecClose()
(*avcodec.Context)(unsafe.Pointer(pCodecCtxOrig)).AvcodecClose()
// 關(guān)閉視頻文件
Inputformatctx.AvformatCloseInput()
// Stop after saving frames of first video straem
break
default:
return errors.New("Didn't find a video stream")
}
}
return nil
}
func main() {
input_filename := "song.mp4"
output_filename := "1280x720_yuv420p.yuv"
avformat.AvRegisterAll()
//解碼視頻流數(shù)據(jù)
FFmpeg_H264DecodeToYUV(input_filename, output_filename)
}
3.3 YUV文件播放
可以使用YUVplayer播放YUV文件,下載地址為YUVplayer播放器
播放時(shí),設(shè)置好播放器的Size和Color
四、YUV編碼為H264
4.1 代碼邏輯及使用API
YUV數(shù)據(jù)編碼為H264格式,其代碼邏輯如下:
- 打開(kāi)輸出文件
func avformat.AvGuessFormat(sn string, f string, mt string) *avformat.OutputFormat
func avformat.AvformatAllocContext() *avformat.Context
func avformat.AvformatAllocOutputContext2(ctx **avformat.Context, o *avformat.OutputFormat, fo string, fi string) int
func avformat.AvIOOpen(url string, flags int) (res *avformat.AvIOContext, err error)
func (*avformat.Context).SetPb(pb *avformat.AvIOContext)
- 創(chuàng)建H264視頻流,并設(shè)置參數(shù)
func (*avformat.Context).AvformatNewStream(c *avformat.AvCodec) *avformat.Stream
func (*avformat.Stream).AvStreamSetRFrameRate(r avcodec.Rational)
- 查找編碼器
func avcodec.AvcodecFindEncoderByName(c string) *avcodec.Codec or
func avcodec.AvcodecFindEncoder(id avcodec.CodecId) *avcodec.Codec
- 配置編碼器
func (*avcodec.Codec).AvcodecAllocContext3() *avcodec.Context
func (*avcodec.Context).SetEncodeParams2(width int, height int, pxlFmt avcodec.PixelFormat, hasBframes bool, gopSize int, profile int)
- 打開(kāi)編碼器
func (*avcodec.Context).AvcodecOpen2(c *avcodec.Codec, d **avcodec.Dictionary) int
- 創(chuàng)建frame并配置
func avutil.AvFrameAlloc() *avutil.Frame
func avcodec.AvpictureGetSize(pf avcodec.PixelFormat, w int, h int) int
func (*avcodec.Picture).AvpictureFill(pt *uint8, pf avcodec.PixelFormat, w int, h int) int
- 寫(xiě)文件頭并創(chuàng)建packet結(jié)構(gòu)
func (*avformat.Context).AvformatWriteHeader(o **avutil.Dictionary) int
func avcodec.AvPacketAlloc() *avcodec.Packet
- 將YUV文件的數(shù)據(jù)讀入frame中,并將frame發(fā)送給解碼器,packet接收編碼后的數(shù)據(jù)
func (*avcodec.Context).AvcodecSendFrame(frame *avcodec.Frame) int
func (*avcodec.Context).AvcodecReceivePacket(packet *avcodec.Packet) int
- 轉(zhuǎn)換packet的Pts、dts
func (*avcodec.Context).AvCodecGetPktTimebase() avcodec.Rational
func (*avformat.Stream).TimeBase() avcodec.Rational
func avutil.AVRescaleQRnd(a int64, bq avutil.Rational, cq avutil.Rational, rnd uint32) int64
func avutil.AVRescaleQRnd(a int64, bq avutil.Rational, cq avutil.Rational, rnd uint32) int64
func (*avcodec.Packet).SetPts(pts int64)
func (*avcodec.Packet).SetDts(dts int64)
- 將packet里的數(shù)據(jù)寫(xiě)入輸出文件中
func (*avformat.Context).AvInterleavedWriteFrame(pkt *avcodec.Packet) int
- 寫(xiě)文件尾,并釋放之前的資源
func (*avformat.Context).AvWriteTrailer() int
4.2 具體代碼實(shí)現(xiàn)
package main
import (
"errors"
"fmt"
"os"
"unsafe"
"github.com/giorgisio/goav/avcodec"
"github.com/giorgisio/goav/avformat"
"github.com/giorgisio/goav/avutil"
)
const (
width = 1280
height = 720
fps = 25
bitrate = 400000
fmtCnt = 100
)
func encode(enc_ctx *avcodec.Context, frame *avutil.Frame, packet *avcodec.Packet, VStream *avformat.Stream, outFmtCtx *avformat.Context) int {
var ret int
if frame != nil {
fmt.Println("frame Send..........")
}
ret = enc_ctx.AvcodecSendFrame((*avcodec.Frame)(unsafe.Pointer(frame)))
if ret < 0 {
fmt.Println("Avcodec Send Frame failed")
return -1
}
for ret >= 0 {
ret = enc_ctx.AvcodecReceivePacket(packet)
fmt.Println("packet size is ", ret, " ", packet.Size())
if ret == avutil.AvErrorEAGAIN || ret == avutil.AvErrorEOF {
return 0
} else if ret < 0 {
continue
}
fmt.Println("finish encode and write data to out_file")
packet.SetStreamIndex(VStream.Index())
input_time_base := enc_ctx.AvCodecGetPktTimebase()
output_time_base := VStream.TimeBase()
input_tmp := (*avutil.Rational)(unsafe.Pointer(&input_time_base))
output_tmp := (*avutil.Rational)(unsafe.Pointer(&output_time_base))
dtscurrent := avutil.AVRescaleQRnd(packet.Dts(), *input_tmp, *output_tmp, uint32(avutil.AV_ROUND_NEAR_INF|avutil.AV_ROUND_PASS_MINMAX))
ptscurrent := avutil.AVRescaleQRnd(packet.Pts(), *input_tmp, *output_tmp, uint32(avutil.AV_ROUND_NEAR_INF|avutil.AV_ROUND_PASS_MINMAX))
packet.SetPts(ptscurrent)
packet.SetDts(dtscurrent)
outFmtCtx.AvInterleavedWriteFrame(packet)
packet.AvPacketUnref()
}
return 0
}
func FFmpeg_YuvEncodeToH264(input_filename string, output_filename string) error {
in_file, err := os.Open(input_filename)
if err != nil {
return errors.New("Open file failed")
}
defer in_file.Close()
var packet *avcodec.Packet
var enc_ctx *avcodec.Context
var frame *avutil.Frame
var outFmtCtx *avformat.Context
ufmt := avformat.AvGuessFormat("H264", output_filename, "")
outFmtCtx = avformat.AvformatAllocContext()
if avformat.AvformatAllocOutputContext2(&outFmtCtx, ufmt, "mp4", output_filename) < 0 {
return errors.New("Cannot alloc output file context.")
}
pb, err := avformat.AvIOOpen(output_filename, avformat.AVIO_FLAG_WRITE)
if err != nil {
return err
}
outFmtCtx.SetPb(pb)
// 創(chuàng)建h264流, 并設(shè)置參數(shù)
var rational avcodec.Rational
rational.Set(1, fps)
VStream := outFmtCtx.AvformatNewStream(nil)
if VStream == nil {
return errors.New("VStream is nil")
}
VStream.AvStreamSetRFrameRate(rational) //設(shè)置25幀每秒,fps為25
// //設(shè)置相關(guān)編碼參數(shù)
codecPara := outFmtCtx.Streams()[VStream.Index()].Codec()
codecPara.SetCodecType(avformat.AVMEDIA_TYPE_VIDEO)
codecPara.SetWidth(width)
codecPara.SetHeight(height)
//查找編碼器
pCodec := avcodec.AvcodecFindEncoderByName("libx264")
if pCodec == nil {
return errors.New("avcodec_find_encoder_by_name fail")
}
//配置編碼器的上下文
enc_ctx = pCodec.AvcodecAllocContext3()
enc_ctx.SetEncodeParams2(width, height, avcodec.AV_PIX_FMT_YUV420P, false, 10, avcodec.FF_PROFILE_H264_HIGH)
enc_ctx.SetTimebase(1, fps) //設(shè)置25幀每秒,fps為25
if int(pCodec.AvcodecAllocContext3().CodecId()) == avcodec.AV_CODEC_ID_H264 {
fmt.Println("H264........")
}
//打開(kāi)編碼器
if enc_ctx.AvcodecOpen2(pCodec, nil) < 0 {
errors.New("Could not open codec-------------")
}
//創(chuàng)建frame并初始化
frame = avutil.AvFrameAlloc()
avutil.AvSetFrame(frame, enc_ctx.Width(), enc_ctx.Height(), int(enc_ctx.PixFmt()))
newSize := avcodec.AvpictureGetSize(enc_ctx.PixFmt(), enc_ctx.Width(), enc_ctx.Height())
picture_buf := avutil.AvMalloc(uintptr(newSize))
avp := (*avcodec.Picture)(unsafe.Pointer(frame))
avp.AvpictureFill((*uint8)(picture_buf), enc_ctx.PixFmt(), enc_ctx.Width(), enc_ctx.Height())
fmt.Println("newSize = ", newSize)
fmt.Println("width:", enc_ctx.Width())
fmt.Println("height:", enc_ctx.Height())
fmt.Println("PixFmt:", enc_ctx.PixFmt())
fmt.Println("Profile:", enc_ctx.Profile())
//寫(xiě)文件頭
if outFmtCtx.AvformatWriteHeader(nil) < 0 {
return errors.New("write header error,outputfile name : " + output_filename)
}
//創(chuàng)建編碼后的數(shù)據(jù)包,用來(lái)存儲(chǔ)frame編碼后的數(shù)據(jù)
packet = avcodec.AvPacketAlloc()
packet.AvNewPacket(newSize)
y_size := enc_ctx.Width() * enc_ctx.Height()
//循環(huán)編碼每一幀
var j int = 0
for i := 0; i < fmtCnt; i++ {
//讀入YUV
buf := make([]byte, newSize)
n, err := in_file.Read(buf)
if err != nil {
return errors.New("read in_file failed....")
}
//將buf數(shù)據(jù)拷貝倒picture_buf
copy((*[1 << 30]byte)(picture_buf)[:newSize:newSize], buf[:n])
pic_ptr := uintptr(picture_buf)
//y
avutil.SetData(frame, 0, (*uint8)(unsafe.Pointer(pic_ptr)))
fmt.Println("data[0]:", avutil.Data(frame)[0])
fmt.Println("*data[0]:", *(avutil.Data(frame)[0]))
//u
avutil.SetData(frame, 1, (*uint8)(unsafe.Pointer(pic_ptr+uintptr(y_size))))
fmt.Println("data[1]:", avutil.Data(frame)[1])
fmt.Println("*data[1]:", *(avutil.Data(frame)[1]))
// v
avutil.SetData(frame, 2, (*uint8)(unsafe.Pointer(pic_ptr+uintptr(y_size*5/4))))
fmt.Println("data[2]:", avutil.Data(frame)[2])
fmt.Println("*data[2]:", *(avutil.Data(frame)[2]))
fmt.Println("準(zhǔn)備編碼 ---------------------")
avutil.FrameSetPts(frame, int64(j))
j = i + 1
//利用編碼器進(jìn)行編碼, 將frame的數(shù)據(jù)傳入packet
if encode(enc_ctx, frame, packet, VStream, outFmtCtx) == -1 {
break
}
}
//flush encoder
encode(enc_ctx, nil, packet, VStream, outFmtCtx)
//寫(xiě)文件尾
outFmtCtx.AvWriteTrailer()
//釋放所有指針資源
enc_ctx.AvcodecClose()
avutil.AvFree(unsafe.Pointer(frame))
if outFmtCtx != nil {
outFmtCtx.Pb().Close()
outFmtCtx.AvformatFreeContext()
}
return nil
}
func main() {
output_filename := "1280x720_yuv420p.yuv"
filename := "result.H264"
avformat.AvRegisterAll()
//編碼碼視頻流數(shù)據(jù)
FFmpeg_YuvEncodeToH264(output_filename, filename)
}
4.3 H264文件播放
H264文件可以用vlc播放器進(jìn)行播放,播放器下載地址為:vlc播放器文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-513238.html
vlc播放器工具欄—編解碼器信息,可以查看視頻的編碼格式以及數(shù)據(jù)丟失率文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-513238.html
到了這里,關(guān)于golang基于FFmpeg實(shí)現(xiàn)視頻H264編解碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!