2023-03-21:音视频解混合(demuxer)为MP3和H264,用go语言编写。

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库。

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

步骤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

在这里插入图片描述

本作品采用《CC 协议》,转载必须注明作者和本文链接
微信公众号:福大大架构师每日一题。最新面试题,涉及golang,rust,mysql,redis,云原生,算法,分布式,网络,操作系统。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
470
粉丝
21
喜欢
37
收藏
22
排名:457
访问:1.9 万
私信
所有博文
社区赞助商