2023-03-21:音視訊解混合(demuxer)為MP3和H264,用go語言編寫。
答案2023-03-21:
# 步驟1:安裝github.com/moonfdd/ffmpeg-go
go get -u github.com/moonfdd/ffmpeg-go
# 步驟2:導入所需的庫
接下來,我們需要導入所需的庫。這些庫包括fmt、os、exec以及FFmpeg庫中的libavcodec、libavdevice、libavformat和libavutil。在本教程中,我們還将使用moonfdd/ffmpeg-go庫,該庫提供了一些便捷的函數和類型定義,可幫助我們更輕松地使用FFmpeg庫。
import (
"fmt"
"os"
"os/exec"
"github.com/moonfdd/ffmpeg-go/ffcommon"
"github.com/moonfdd/ffmpeg-go/libavcodec"
"github.com/moonfdd/ffmpeg-go/libavdevice"
"github.com/moonfdd/ffmpeg-go/libavformat"
"github.com/moonfdd/ffmpeg-go/libavutil"
)
# 步驟3:設定FFmpeg庫路徑
在使用FFmpeg庫之前,我們需要設定FFmpeg庫的路徑。您可以通過設定環境變量來實作這一點,也可以直接調用FFmpeg庫的SetXxxPath函數進行設定。
// 設定環境變量
os.Setenv("Path", os.Getenv("Path")+";./lib")
// 設定FFmpeg庫路徑
ffcommon.SetAvutilPath("./lib/avutil-56.dll")
ffcommon.SetAvcodecPath("./lib/avcodec-58.dll")
ffcommon.SetAvdevicePath("./lib/avdevice-58.dll")
ffcommon.SetAvfilterPath("./lib/avfilter-56.dll")
ffcommon.SetAvformatPath("./lib/avformat-58.dll")
ffcommon.SetAvpostprocPath("./lib/postproc-55.dll")
ffcommon.SetAvswresamplePath("./lib/swresample-3.dll")
ffcommon.SetAvswscalePath("./lib/swscale-5.dll")
# 步驟4:定義必要的變量
在使用FFmpeg庫之前,我們需要定義一些必要的變量。這些變量包括輸入檔案名、輸出音頻檔案名、輸出視訊檔案名、輸入格式上下文、輸出音頻格式上下文、輸出視訊格式上下文、AVPacket等。在本教程中,我們還定義了用于儲存視訊索引和音頻索引的變量。
var ifmtCtx, ofmtCtxAudio, ofmtCtxVideo *libavformat.AVFormatContext
var packet libavcodec.AVPacket
var videoIndex ffcommon.FInt = -1
var audioIndex ffcommon.FInt = -1
var ret ffcommon.FInt = 0
inFileName := "./resources/big_buck_bunny.mp4"
outFilenameAudio := "./out/a22.aac"
outFilenameVideo := "./out/a22.h264"
# 步驟5:注冊裝置
在使用FFmpeg庫之前,我們需要先注冊裝置。您可以使用libavdevice.AvdeviceRegisterAll()函數來注冊所有支援的裝置。
libavdevice.AvdeviceRegisterAll()
# 步驟6:打開輸入流
在從音視訊檔案中分離出音頻和視訊之前,我們需要打開音視訊檔案的輸入流。您可以使用libavformat.AvformatOpenInput函數來打開輸入流,并使用ifmtCtx參數儲存輸入流的上下文。
if libavformat.AvformatOpenInput(&ifmtCtx, inFileName, nil, nil) < 0 {
fmt.Printf("Could not open input file '%s'\n", inFileName)
return
}
defer ifmtCtx.AvformatCloseInput()
# 步驟7:讀取媒體資訊
打開輸入流後,我們需要讀取音視訊檔案的媒體資訊。您可以使用libavformat.AvformatFindStreamInfo函數來讀取媒體資訊,并使用libavutil.AvDumpFormat函數将媒體資訊輸出到控制台。
if ifmtCtx.AvformatFindStreamInfo(nil) < 0 {
fmt.Println("Could not find stream information")
return
}
libavutil.AvDumpFormat(ifmtCtx, 0, inFileName, 0)
# 步驟8:查找音頻和視訊流
在讀取媒體資訊後,我們需要查找音頻和視訊流。您可以使用libavformat.AvformatFindStreamInfo函數來查找音頻和視訊流,并使用videoIndex和audioIndex變量儲存視訊流和音頻流的索引。
for i := 0; i < int(ifmtCtx.NbStreams()); i++ {
codecParams := ifmtCtx.Streams()[i].CodecParameters()
codecType := codecParams.AvCodecGetType()
switch codecType {
case libavutil.AVMEDIA_TYPE_VIDEO:
if videoIndex == -1 {
videoIndex = ffcommon.FInt(i)
}
case libavutil.AVMEDIA_TYPE_AUDIO:
if audioIndex == -1 {
audioIndex = ffcommon.FInt(i)
}
}
}
if videoIndex == -1 || audioIndex == -1 {
fmt.Println("Could not find video or audio stream")
return
}
# 步驟9:打開輸出流
在查找音頻和視訊流後,我們需要打開輸出流,以便将分離出的音頻和視訊寫入檔案。您可以使用libavformat.AvformatAllocOutputContext2函數建立輸出格式上下文,并使用ofmtCtxAudio和ofmtCtxVideo變量儲存輸出格式上下文。
// 打開輸出音頻流
if ofmtCtxAudio = libavformat.AvformatAllocOutputContext2(nil, nil, "", outFilenameAudio); ofmtCtxAudio == nil {
fmt.Printf("could not create output context for '%s'\n", outFilenameAudio)
return
}
// 打開輸出視訊流
if ofmtCtxVideo = libavformat.AvformatAllocOutputContext2(nil, nil, "h264", outFilenameVideo); ofmtCtxVideo == nil {
fmt.Printf("could not create output context for '%s'\n", outFilenameVideo)
return
}
# 步驟10:寫入檔案頭
打開輸出流後,我們需要寫入檔案頭。您可以使用libavformat.AvformatWriteHeader函數來寫入檔案頭。
// 寫入音頻檔案頭
if (ofmtCtxAudio.Oformat().Flags() & libavformat.AVFMT_NOFILE) == 0 {
if ret = ofmtCtxAudio.AvioOpen(nil, libavformat.AVIO_FLAG_WRITE); ret < 0 {
fmt.Printf("could not open output file '%s'\n", outFilenameAudio)
return
}
defer ofmtCtxAudio.AvioClose()
}
if ret = ofmtCtxAudio.AvformatWriteHeader(nil); ret < 0 {
fmt.Println("Could not write output file header")
return
}
// 寫入視訊檔案頭
if (ofmtCtxVideo.Oformat().Flags() & libavformat.AVFMT_NOFILE) == 0 {
if ret = ofmtCtxVideo.AvioOpen(nil, libavformat.AVIO_FLAG_WRITE); ret < 0 {
fmt.Printf("could not open output file '%s'\n", outFilenameVideo)
return
}
defer ofmtCtxVideo.AvioClose()
}
if ret = ofmtCtxVideo.AvformatWriteHeader(nil); ret < 0 {
fmt.Println("Could not write output file header")
return
}
# 步驟11:分離音頻和視訊
寫入檔案頭後,我們可以開始分離音頻和視訊了。您可以使用libavformat.AvReadFrame函數讀取音視訊幀,并根據音頻或視訊流的索引将音頻幀寫入音頻檔案,将視訊幀寫入視訊檔案。
for {
if ret = ifmtCtx.AvReadFrame(&packet); ret <0 {
break
}
defer packet.AvPacketUnref()
if packet.StreamIndex() == audioIndex {
// 寫入音頻流
if ret = ofmtCtxAudio.AvInterleavedWriteFrame(&packet); ret < 0 {
fmt.Printf("error while writing audio frame: %v\n", ret)
return
}
} else if packet.StreamIndex() == videoIndex {
// 寫入視訊流
if ret = ofmtCtxVideo.AvInterleavedWriteFrame(&packet); ret < 0 {
fmt.Printf("error while writing video frame: %v\n", ret)
return
}
}
}
# 步驟12:寫入檔案尾
完成音視訊分離後,我們需要寫入檔案尾。您可以使用libavformat.AvWriteTrailer函數來寫入檔案尾。
if ret = ofmtCtxAudio.AvWriteTrailer(); ret < 0 {
fmt.Println("Could not write output file trailer")
return
}
// 寫入視訊檔案尾
if ret = ofmtCtxVideo.AvWriteTrailer(); ret < 0 {
fmt.Println("Could not write output file trailer")
return
}
# 完整代碼
// https://feater.top/ffmpeg/ffmpeg-demuxer-video-to-mp3-and-h264
package main
import (
"fmt"
"os"
"os/exec"
"github.com/moonfdd/ffmpeg-go/ffcommon"
"github.com/moonfdd/ffmpeg-go/libavcodec"
"github.com/moonfdd/ffmpeg-go/libavdevice"
"github.com/moonfdd/ffmpeg-go/libavformat"
"github.com/moonfdd/ffmpeg-go/libavutil"
)
func open_codec_context(streamIndex *ffcommon.FInt, ofmtCtx **libavformat.AVFormatContext, ifmtCtx *libavformat.AVFormatContext, type0 libavutil.AVMediaType) ffcommon.FInt {
var outStream, inStream *libavformat.AVStream
// int ret = -1, index = -1;
var ret ffcommon.FInt = -1
var index ffcommon.FInt = -1
index = ifmtCtx.AvFindBestStream(type0, -1, -1, nil, 0)
if index < 0 {
fmt.Printf("can't find %s stream in input file\n", libavutil.AvGetMediaTypeString(type0))
return ret
}
inStream = ifmtCtx.GetStream(uint32(index))
outStream = (*ofmtCtx).AvformatNewStream(nil)
if outStream == nil {
fmt.Printf("failed to allocate output stream\n")
return ret
}
ret = libavcodec.AvcodecParametersCopy(outStream.Codecpar, inStream.Codecpar)
if ret < 0 {
fmt.Printf("failed to copy codec parametes\n")
return ret
}
outStream.Codecpar.CodecTag = 0
*streamIndex = index
return 0
}
func main() {
os.Setenv("Path", os.Getenv("Path")+";./lib")
ffcommon.SetAvutilPath("./lib/avutil-56.dll")
ffcommon.SetAvcodecPath("./lib/avcodec-58.dll")
ffcommon.SetAvdevicePath("./lib/avdevice-58.dll")
ffcommon.SetAvfilterPath("./lib/avfilter-56.dll")
ffcommon.SetAvformatPath("./lib/avformat-58.dll")
ffcommon.SetAvpostprocPath("./lib/postproc-55.dll")
ffcommon.SetAvswresamplePath("./lib/swresample-3.dll")
ffcommon.SetAvswscalePath("./lib/swscale-5.dll")
genDir := "./out"
_, err := os.Stat(genDir)
if err != nil {
if os.IsNotExist(err) {
os.Mkdir(genDir, 0777) // Everyone can read write and execute
}
}
inFileName := "./resources/big_buck_bunny.mp4"
outFilenameAudio := "./out/a22.aac"
outFilenameVideo := "./out/a22.h264"
var ifmtCtx, ofmtCtxAudio, ofmtCtxVideo *libavformat.AVFormatContext
var packet libavcodec.AVPacket
var videoIndex ffcommon.FInt = -1
var audioIndex ffcommon.FInt = -1
var ret ffcommon.FInt = 0
//注冊裝置
libavdevice.AvdeviceRegisterAll()
for {
//打開輸入流
if libavformat.AvformatOpenInput(&ifmtCtx, inFileName, nil, nil) < 0 {
fmt.Printf("Cannot open input file.\n")
break
}
//擷取流資訊
if ifmtCtx.AvformatFindStreamInfo(nil) < 0 {
fmt.Printf("Cannot find stream info in input file.\n")
break
}
//建立輸出上下文:視訊
libavformat.AvformatAllocOutputContext2(&ofmtCtxVideo, nil, "", outFilenameVideo)
if ofmtCtxVideo == nil {
fmt.Printf("can't create video output context")
break
}
//建立輸出上下文:音頻
libavformat.AvformatAllocOutputContext2(&ofmtCtxAudio, nil, "", outFilenameAudio)
if ofmtCtxAudio == nil {
fmt.Printf("can't create audio output context")
break
}
ret = open_codec_context(&videoIndex, &ofmtCtxVideo, ifmtCtx, libavutil.AVMEDIA_TYPE_VIDEO)
if ret < 0 {
fmt.Printf("can't decode video context\n")
break
}
ret = open_codec_context(&audioIndex, &ofmtCtxAudio, ifmtCtx, libavutil.AVMEDIA_TYPE_AUDIO)
if ret < 0 {
fmt.Printf("can't decode video context\n")
break
}
//Dump Format------------------
fmt.Printf("\n==============Input Video=============\n")
ifmtCtx.AvDumpFormat(0, inFileName, 0)
fmt.Printf("\n==============Output Video============\n")
ofmtCtxVideo.AvDumpFormat(0, outFilenameVideo, 1)
fmt.Printf("\n==============Output Audio============\n")
ofmtCtxAudio.AvDumpFormat(0, outFilenameAudio, 1)
fmt.Printf("\n======================================\n")
//打開輸出檔案:視訊
if ofmtCtxVideo.Oformat.Flags&libavformat.AVFMT_NOFILE == 0 {
if libavformat.AvioOpen(&ofmtCtxVideo.Pb, outFilenameVideo, libavformat.AVIO_FLAG_WRITE) < 0 {
fmt.Printf("can't open output file: %s\n", outFilenameVideo)
break
}
}
//打開輸出檔案:音頻
if ofmtCtxAudio.Oformat.Flags&libavformat.AVFMT_NOFILE == 0 {
if libavformat.AvioOpen(&ofmtCtxAudio.Pb, outFilenameAudio, libavformat.AVIO_FLAG_WRITE) < 0 {
fmt.Printf("can't open output file: %s\n", outFilenameVideo)
break
}
}
//寫檔案頭
if ofmtCtxVideo.AvformatWriteHeader(nil) < 0 {
fmt.Printf("Error occurred when opening video output file\n")
break
}
if ofmtCtxAudio.AvformatWriteHeader(nil) < 0 {
fmt.Printf("Error occurred when opening audio output file\n")
break
}
for {
var ofmtCtx *libavformat.AVFormatContext
var inStream, outStream *libavformat.AVStream
if ifmtCtx.AvReadFrame(&packet) < 0 {
break
}
inStream = ifmtCtx.GetStream(packet.StreamIndex)
if packet.StreamIndex == uint32(videoIndex) {
outStream = ofmtCtxVideo.GetStream(0)
ofmtCtx = ofmtCtxVideo
} else if packet.StreamIndex == uint32(audioIndex) {
outStream = ofmtCtxAudio.GetStream(0)
ofmtCtx = ofmtCtxAudio
} else {
continue
}
//convert PTS/DTS
packet.Pts = libavutil.AvRescaleQRnd(packet.Pts, inStream.TimeBase, outStream.TimeBase,
libavutil.AV_ROUND_NEAR_INF|libavutil.AV_ROUND_PASS_MINMAX)
packet.Dts = libavutil.AvRescaleQRnd(packet.Dts, inStream.TimeBase, outStream.TimeBase,
libavutil.AV_ROUND_NEAR_INF|libavutil.AV_ROUND_PASS_MINMAX)
packet.Duration = libavutil.AvRescaleQ(packet.Duration, inStream.TimeBase, outStream.TimeBase)
packet.Pos = -1
packet.StreamIndex = 0
//write
if ofmtCtx.AvInterleavedWriteFrame(&packet) < 0 {
fmt.Printf("Error muxing packet\n")
break
}
packet.AvPacketUnref()
}
//write file trailer
ofmtCtxVideo.AvWriteTrailer()
ofmtCtxAudio.AvWriteTrailer()
break
}
libavformat.AvformatCloseInput(&ifmtCtx)
if ofmtCtxVideo != nil && (ofmtCtxVideo.Oformat.Flags&libavformat.AVFMT_NOFILE) == 0 {
ofmtCtxVideo.Pb.AvioClose()
}
if ofmtCtxAudio != nil && (ofmtCtxAudio.Oformat.Flags&libavformat.AVFMT_NOFILE) == 0 {
ofmtCtxAudio.Pb.AvioClose()
}
ofmtCtxVideo.AvformatFreeContext()
ofmtCtxAudio.AvformatFreeContext()
fmt.Println("-----------------------------------------")
go func() {
_, err = exec.Command("./lib/ffplay.exe", outFilenameAudio).Output()
if err != nil {
fmt.Println("play err = ", err)
}
}()
_, err = exec.Command("./lib/ffplay.exe", outFilenameVideo).Output()
if err != nil {
fmt.Println("play err = ", err)
}
}
# 執行結果
執行指令:
go run ./examples/a22.video_demuxer_mp42h264mp3/main.go