前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >2022-04-27:用go语言重写ffmpeg的remuxing.c示例。

2022-04-27:用go语言重写ffmpeg的remuxing.c示例。

作者头像
福大大架构师每日一题
发布于 2023-06-09 02:11:22
发布于 2023-06-09 02:11:22
37500
代码可运行
举报
运行总次数:0
代码可运行

2022-04-27:用go语言重写ffmpeg的remuxing.c示例。

答案2022-04-27:

ffmpeg的remuxing.c是一个用于将多媒体文件从一种容器格式转换为另一种容器格式的命令行工具。它可以将音频、视频和字幕等元素从源文件中提取出来,并按照用户指定的方式重新封装到目标文件中。在本篇文章中,我将对ffmpeg的remuxing.c进行介绍,并讨论其关键功能和技术实现。

1. remuxing.c的主要功能

remuxing.c主要有两个关键功能:提取和重封装。在提取阶段,remuxing.c会解析源文件的格式,并将其中的音频、视频和字幕等元素提取出来。在重封装阶段,remuxing.c将这些元素重新封装为另一种格式,并生成目标文件。

remuxing.c支持多种输入和输出格式,包括常见的MP4、AVI、MKV、FLV等格式。用户可以通过指定命令行参数来选择源文件和目标文件格式,并控制重封装过程中的各种选项,例如视频编码器、音频采样率、字幕格式等。

除了基本的提取和重封装功能之外,remuxing.c还支持其他高级功能,例如从流媒体服务器拉取数据、实时流处理、特定元素的删除和添加等。

2. remuxing.c技术实现

remuxing.c的技术实现主要涉及以下几个方面:

2.1 容器格式解析和重构

remuxing.c需要能够识别并解析多种容器格式,以便提取其中的音频、视频和字幕等元素。为了实现这一功能,remuxing.c使用了FFmpeg中的AVFormatContext结构体,并利用其封装和解封装函数进行文件格式的解析和重构。在提取阶段,remuxing.c通过遍历媒体文件的AVStream对象来获取其中的音频流、视频流和字幕流等元素,然后将它们存储在合适的AVCodecContext对象中。在重封装阶段,remuxing.c则使用AVOutputFormat结构体和AVStream对象来指定目标文件的格式和编码方式。

2.2 媒体数据的解码和编码

在提取阶段,remuxing.c需要将从源文件中提取出来的音频、视频和字幕等元素进行解码,以便后续的处理和重封装。为了实现这一功能,remuxing.c使用了FFmpeg中的AVCodecContext结构体和相应的解码器函数,例如avcodec_send_packet()和avcodec_receive_frame()等。在重封装阶段,remuxing.c则需要将解码后的音频、视频和字幕等元素进行编码,以便生成目标文件。为此,它使用了AVCodecContext结构体和相应的编码器函数,例如avcodec_send_frame()和avcodec_receive_packet()等。

2.3 数据流的复制和过滤

在提取阶段,remuxing.c需要将从源文件中提取出来的音频、视频和字幕等元素进行复制,以便后续重封装时使用。为此,remuxing.c使用了FFmpeg中的AVPacket结构体和av_packet_copy_props()函数等,实现了数据流的复制操作。

在重封装阶段,remuxing.c还支持对特定元素的过滤和修改。例如,用户可以通过指定命令行参数来删除特定的音频或视频流,或者修改音频采样率等参数。为了实现这一功能,remuxing.c使用了AVFilterGraph结构体和相应的过滤器函数,例如avfilter_graph_create_filter()和av_buffersink_get_frame()等。

2.4 码率控制和优化

在重封装阶段,remuxing.c需要根据用户指定的编码参数和目标文件格式等因素,对音视频数据进行适当的码率控制和优化,以便生成高质量的目标文件。为此,remuxing.c使用了FFmpeg中的AVCodecContext结构体和相关的码率控制函数,例如avcodec_set_bitrate()和av_opt_set()等。

3. 总结

ffmpeg的remuxing.c是一个非常强大和灵活的多媒体文件转换工具,它能够解析多种容器格式,并提取其中的音频、视频和字幕等元素,然后按照用户指定的方式重新封装为目标文件。通过使用FFmpeg中的AVFormatContext、AVCodecContext和AVFilterGraph等结构体,以及相应的解封装、解码、编码、复制和过滤函数,remuxing.c实现了这些功能,并支持多种进阶选项,例如流式处理和码率控制等。因此,remuxing.c是一个非常实用和强大的多媒体工具,适用于各种媒体转换和处理场景。

4.golang重写

这个Go程序使用FFmpeg库来对媒体文件进行重封装,以更改容器格式或编解码器参数。以下是代码的步骤:

(1).导入必要的依赖项,如FFmpeg库和unsafe包。

(2).定义全局变量和函数来设置输出路径、检查目录是否存在、打印Packet信息等。

(3).定义主函数"main",在其中设置各种FFmpeg库的路径、创建输出目录、调用main0函数实现文件重封装。

(4).定义函数"main0",其中初始化输入和输出文件的AVFormatContext,获取输入文件流信息,分配输出文件的上下文并根据输入流创建相应的输出流,将所有流映射到输出上下文,并写入输出文件头部。

(4.1).首先声明需要使用的变量:ifmt_ctx, ofmt_ctx, pkt, in_filename, out_filename, i, stream_index, stream_mapping和stream_mapping_size。

(4.2).打开输入文件并且获取输入文件的流信息。如果无法打开则输出错误并返回ret值。

(4.3).输出input file的音视频流信息。

(4.4).根据输出文件名获取输出文件的 AVFormatContext上下文。

(4.5).分配一个数组来映射输入文件流和输出文件流。如果无法分配,则返回错误码。

(4.6).将输出文件相关的参数初始化为输入文件的参数

(4.7).遍历所有输入流,将输入流映射到相应的输出流并将其添加到输出文件的AVFormatContext中。

(4.8).输出output file的音视频流信息。

(4.9).如果需要,打开输出文件并将其与相应的AVIOContext关联。

(4.10).写入输出文件头部。

(4.11).循环读取输入文件的AVPacket,并根据该Packet所在的输入流信息查找对应的输出流。

(4.12).将时间戳和持续时间转换为输出流格式。

(4.13).将该Packet复制到输出流并写入输出文件。

(4.14).循环结束后,写完输出文件头和文件尾。

(4.15).手动关闭输入文件和释放资源。

(4.16).最后,检查ret值是否小于0且不等于libavutil.AVERROR_EOF,如果是则输出错误信息。

(4.17).在循环中,判断Packet所在的输入流是否为音频、视频或字幕流。如果不是这些流,则将该流映射到输出流-1并跳过。

(4.18).根据流映射数组(stream_mapping)查找对应的输出流,计算时间戳和持续时间等参数,并将Packet复制到输出流并写入输出文件。如果出现错误,输出错误信息并退出循环。

(4.19).释放Packet的资源。

(4.20).写完所有Packet后,写入输出文件的文件尾部。

(4.21).关闭输入文件和输出文件。如果输出文件有相关联的AVIOContext,则同时关闭。

(4.22).最后,如果ret值小于0且不等于libavutil.AVERROR_EOF,则输出错误信息。

(5).循环读取输入文件的AVPacket,检索与当前Packet相关联的输入流和输出流,计算时间戳和持续时间等参数,并将Packet复制到输出流并写入输出文件。

(6).在结束循环后,写入输出文件的文件尾并释放所分配的资源。

总之,这个Go程序使用FFmpeg库来对媒体文件进行重封装,主要实现过程是通过读取输入文件的AVPacket,将其复制到相应的输出文件中,并确保时间戳和持续时间等参数正确设置。

命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
go run ./examples/internalexamples/remuxing/main.go ./resources/big_buck_bunny.mp4 ./out/remuxing.flv
./lib/ffplay ./out/remuxing.flv

golang完整代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

import (
    "fmt"
    "os"
    "unsafe"

    "github.com/moonfdd/ffmpeg-go/ffcommon"
    "github.com/moonfdd/ffmpeg-go/libavcodec"
    "github.com/moonfdd/ffmpeg-go/libavformat"
    "github.com/moonfdd/ffmpeg-go/libavutil"
)

func main0() (ret ffcommon.FInt) {
    var ofmt *libavformat.AVOutputFormat
    var ifmt_ctx, ofmt_ctx *libavformat.AVFormatContext
    var pkt libavcodec.AVPacket
    var in_filename, out_filename string
    var i ffcommon.FInt
    var stream_index ffcommon.FInt = 0
    var stream_mapping *ffcommon.FInt
    var stream_mapping_size ffcommon.FInt = 0

    if len(os.Args) < 3 {
        fmt.Printf("usage: %s input output\nAPI example program to remux a media file with libavformat and libavcodec.\nThe output format is guessed according to the file extension.\n\n", os.Args[0])
        return 1
    }

    in_filename = os.Args[1]
    out_filename = os.Args[2]

    ret = libavformat.AvformatOpenInput(&ifmt_ctx, in_filename, nil, nil)
    if ret < 0 {
        fmt.Printf("Could not open input file '%s'", in_filename)
        goto end
    }

    ret = ifmt_ctx.AvformatFindStreamInfo(nil)
    if ret < 0 {
        fmt.Printf("Failed to retrieve input stream information")
        goto end
    }

    ifmt_ctx.AvDumpFormat(0, in_filename, 0)

    libavformat.AvformatAllocOutputContext2(&ofmt_ctx, nil, "", out_filename)
    if ofmt_ctx == nil {
        fmt.Printf("Could not create output context\n")
        ret = libavutil.AVERROR_UNKNOWN
        goto end
    }

    stream_mapping_size = int32(ifmt_ctx.NbStreams)
    stream_mapping = (*int32)(unsafe.Pointer(libavutil.AvMalloczArray(uint64(stream_mapping_size), 4)))
    if stream_mapping == nil {
        ret = -libavutil.ENOMEM
        goto end
    }

    ofmt = ofmt_ctx.Oformat

    for i = 0; i < int32(ifmt_ctx.NbStreams); i++ {
        var out_stream *libavformat.AVStream
        in_stream := ifmt_ctx.GetStream(uint32(i))
        in_codecpar := in_stream.Codecpar

        if in_codecpar.CodecType != libavutil.AVMEDIA_TYPE_AUDIO &&
            in_codecpar.CodecType != libavutil.AVMEDIA_TYPE_VIDEO &&
            in_codecpar.CodecType != libavutil.AVMEDIA_TYPE_SUBTITLE {
            *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(stream_mapping)) + uintptr(4*i))) = -1
            continue
        }

        *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(stream_mapping)) + uintptr(4*i))) = stream_index
        stream_index++

        out_stream = ofmt_ctx.AvformatNewStream(nil)
        if out_stream == nil {
            fmt.Printf("Failed allocating output stream\n")
            ret = libavutil.AVERROR_UNKNOWN
            goto end
        }

        ret = libavcodec.AvcodecParametersCopy(out_stream.Codecpar, in_codecpar)
        if ret < 0 {
            fmt.Printf("Failed to copy codec parameters\n")
            goto end
        }
        out_stream.Codecpar.CodecTag = 0
    }
    ofmt_ctx.AvDumpFormat(0, out_filename, 1)

    if ofmt.Flags&libavformat.AVFMT_NOFILE == 0 {
        ret = libavformat.AvioOpen(&ofmt_ctx.Pb, out_filename, libavformat.AVIO_FLAG_WRITE)
        if ret < 0 {
            fmt.Printf("Could not open output file '%s'", out_filename)
            goto end
        }
    }

    ret = ofmt_ctx.AvformatWriteHeader(nil)
    if ret < 0 {
        fmt.Printf("Error occurred when opening output file\n")
        goto end
    }

    for {
        var in_stream, out_stream *libavformat.AVStream

        ret = ifmt_ctx.AvReadFrame(&pkt)
        if ret < 0 {
            break
        }

        in_stream = ifmt_ctx.GetStream(pkt.StreamIndex)
        if pkt.StreamIndex >= uint32(stream_mapping_size) ||
            *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(stream_mapping)) + uintptr(4*pkt.StreamIndex))) < 0 {
            pkt.AvPacketUnref()
            continue
        }

        pkt.StreamIndex = uint32(*(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(stream_mapping)) + uintptr(4*pkt.StreamIndex))))
        out_stream = ofmt_ctx.GetStream(pkt.StreamIndex)
        log_packet(ifmt_ctx, &pkt, "in")

        /* copy packet */
        pkt.Pts = libavutil.AvRescaleQRnd(pkt.Pts, in_stream.TimeBase, out_stream.TimeBase, libavutil.AV_ROUND_NEAR_INF|libavutil.AV_ROUND_PASS_MINMAX)
        pkt.Dts = libavutil.AvRescaleQRnd(pkt.Dts, in_stream.TimeBase, out_stream.TimeBase, libavutil.AV_ROUND_NEAR_INF|libavutil.AV_ROUND_PASS_MINMAX)
        pkt.Duration = libavutil.AvRescaleQ(pkt.Duration, in_stream.TimeBase, out_stream.TimeBase)
        pkt.Pos = -1
        log_packet(ofmt_ctx, &pkt, "out")

        ret = ofmt_ctx.AvInterleavedWriteFrame(&pkt)
        if ret < 0 {
            fmt.Printf("Error muxing packet\n")
            break
        }
        pkt.AvPacketUnref()
    }

    ofmt_ctx.AvWriteTrailer()
end:

    libavformat.AvformatCloseInput(&ifmt_ctx)

    /* close output */
    if ofmt_ctx != nil && ofmt.Flags&libavformat.AVFMT_NOFILE == 0 {
        libavformat.AvioClosep(&ofmt_ctx.Pb)
    }
    ofmt_ctx.AvformatFreeContext()

    libavutil.AvFreep(uintptr(unsafe.Pointer(&stream_mapping)))

    if ret < 0 && ret != libavutil.AVERROR_EOF {
        fmt.Printf("Error occurred: %s\n", libavutil.AvErr2str(ret))
        return 1
    }

    return 0
}

func log_packet(fmt_ctx *libavformat.AVFormatContext, pkt *libavcodec.AVPacket, tag string) {
    time_base := &fmt_ctx.GetStream(pkt.StreamIndex).TimeBase

    fmt.Printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
        tag,
        libavutil.AvTs2str(pkt.Pts), libavutil.AvTs2timestr(pkt.Pts, time_base),
        libavutil.AvTs2str(pkt.Dts), libavutil.AvTs2timestr(pkt.Dts, time_base),
        libavutil.AvTs2str(pkt.Duration), libavutil.AvTs2timestr(pkt.Duration, time_base),
        pkt.StreamIndex)
}

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

    main0()
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 福大大架构师每日一题 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
我看懂了,大受震撼!看大佬一年经验横扫各大offer
之前写过很多篇关于面试的文章,总是在提面试前要看面经。但是到哪里看,怎么看,从面经当中提取哪些信息,可能很多小伙伴还是一头雾水。
TechFlow-承志
2022/08/26
2820
我看懂了,大受震撼!看大佬一年经验横扫各大offer
IT大咖工作十年总结的面试真经
问题导读 1.你认为面试常问的问题有哪些? 2.回答面试问题的原则是什么? 3.你认为该如何面试? 在about云vip群,铁粉群中,经常看到学员会遇到面试困惑和难题,同时又到了金九银十面试跳槽的季节,相信很多人会遇到面试同样的问题,特别是学生和转行人员,对于面试认识,认知这里做了下总结,相信对大家有所帮助,内容如下: 1.面试流程 2.面试常问问题 3.入行新手该如何写项目经验 4.面试注意问题
用户1410343
2018/10/08
1.7K0
站在面试官角度拆解面试20人的体会
大家好,我是雁卿,之前由于职业发展规划,面试了10多家公司,分享了一些面试题目和作为求职者的面试经验。
程序媛淼淼
2022/09/01
6070
站在面试官角度拆解面试20人的体会
一个后端朋友面试一个月的经验总结
朋友坐标北京,裸辞在家找工作。线上面试一共58场,投递公司包含a轮-c轮、上市公司或者知名互联网公司,除了中途放弃面试或者谈薪阶段放弃之外,基本做到面试一路绿灯,最后拿到了包括阿里、字节等offer。
全菜工程师小辉
2021/09/10
1.3K0
历经70+场面试,我发现了大厂面试的bug,并总结其中心得
每轮面试的整个流程一般分三部分,第一部让你做个自我介绍,第二部分面试官考察专业能力环节,最后一部分是反问面试官环节。
马士兵的朋友圈
2022/07/30
1.3K0
11年软件测试,求职经验分享
软件测试领域 11 年,目前在一家企业担任测试主管,在这 11 年期间我总共经历了 4 份工作,这篇文章我依据整个求职过程从下面几点展开:
FunTester
2023/08/04
2460
11年软件测试,求职经验分享
手把手教你如何面试,你要的我都有(简历篇)
连续好几周,在一些渠道看到关于年底优化的故事,很多小伙伴要么自己中招,要么眼睁睁看着身边同事中招,充满焦虑。亦或者有些小伙伴本身就有被优化的打算,也趁此抓住机会重新寻找,为后面更好的发展做准备。
程序员小跃
2019/12/24
6400
手把手教你如何面试,你要的我都有(简历篇)
萌新iOS面试官迷你厂第一视角
今年的环境很差,很多大厂继去年的寒冬之后,今年又裁了一波(本地就有两个大厂上了新闻-。-),我有两个哥们也是最近进入了找工作的状态。由于我之前做过一段时间的面试官,负责了一段时间iOS端的技术招聘,他们也咨询了我不少关于这方面的细节,借着这个机会,以一个初级面试官第一视角介绍下招聘方的想法。
小蠢驴打代码
2020/01/02
7450
如何通过腾讯、字节跳动、网易的面试?
我是2021年毕业、双非一本,在春招中拿到的offer有:腾讯、字节跳动、网易、京东、美团、大众汽车。
猴子聊数据分析
2020/05/21
1.2K0
程序员跳槽时,如何高效地准备面试?
今天和大家分享的主题是「程序员跳槽时,如何高效地准备面试?」,但其实今天主要涉及到的是 HR 在面试时有哪些套路,这样可以见招拆招,斩获 offer!
技术zhai
2019/02/15
1K0
Java程序员五面阿里分享 逆袭成功 太不容易了!
拿到阿里实习offer,经历了5次面试,其中4轮技术面,1轮HR面试。在这里分享一下自己的面试经验和学习心得。希望能够帮助更多的小伙伴。
美的让人心动
2019/06/19
4240
Java程序员五面阿里分享 逆袭成功 太不容易了!
Android:出身双非学校,头条offer也拿得下!3月系统复习入职,确定方向比努力更重要
最近想找重新找份好点的工作,而一些offer给出的条件是,至少211学校,有点无奈。
Android技术干货分享
2021/08/06
3910
Android:出身双非学校,头条offer也拿得下!3月系统复习入职,确定方向比努力更重要
怎么让面试官喜欢你?
最近不少小伙伴在找工作,市场很卷,如何获得面试官的青睐,这里面有会一些经验,今天我们就来聊下
微观技术
2022/12/29
2960
怎么让面试官喜欢你?
(内幕干货)— 5步教你成功求职进入BAT
简历是突出自我亮点的工作或学习经历和自我介绍。所以写简历要把握两个重点,工作经历要突出重点,自我介绍要写全。
烂猪皮
2019/04/25
6960
(内幕干货)— 5步教你成功求职进入BAT
混迹职场多年的你,面试真的准备好了吗?
每一次面试,都是一场博弈。面试,说到底是供需双方心理上的较量。是用人方与求职者双向沟通的一个环节,是通过相互交流促进了解,进而达成录取意向的过程。
小博测试成长之路
2021/03/07
3580
技术面试老是有劲使不出,该怎么办?
又到了一年金三银四,回想到很多年前我刚参加工作时的面试经历,那时都是呆呆地等着面试官问问题,被问到一些自己并不熟悉的问题时要不就是思考半天也切不中要点,要不就只能无奈地回答并不清楚了。其实不管是经验不足的初级开发,还是面临更高要求的资深开发,在面试上都会有一样的困扰:如何在掌握的知识有限的情况下,完成好一场高质量的面试呢?
烂猪皮
2019/03/11
9320
技术面试老是有劲使不出,该怎么办?
十年编程经验一朝面试被刷,技术面试如何提升表现?
又是一年金三银四,不同以往的是,当前的职场环境已经不再是那个双向奔赴的美好时代了。求职者在变多,HC 在变少,岗位要求还更高了,面对这样的困境,技术人员应该如何突围? 腾讯云开发者社区特邀前贝壳金服小微企业生态 CTO、腾讯云 TVP ,有着多年技术管理经验的史海峰老师,为大家分享了技术面试中的那些弯弯绕绕,分析了工程师面试通过率低现象背后的问题,并提出了 10 大提升面试表现的策略,希望能帮助大家,找到心仪的工作!
腾讯云开发者
2024/04/18
6670
十年编程经验一朝面试被刷,技术面试如何提升表现?
感恩这半年的经历,希望在以后的日子里,不负众望。(秋招心得)
俗话说:“金九银十铜十一”。相信很多小伙伴都经历了跟我一样的秋招历程,从八月份开始准备秋招,制作简历、复习技术栈、背八股文,疯狂的投简历,到九月分开始陆续的接到面试邀请,紧接着邮箱里堆满了感谢信,最后收到录用书。经历过几个月的努力,本人也收到了心仪的Offer,对此我有以下感悟,希望对正在找工作以及准备找工作的小伙伴儿有一定的帮助!
百思不得小赵
2022/12/01
2760
感恩这半年的经历,希望在以后的日子里,不负众望。(秋招心得)
金三银四面试回来,我想跟程序员们谈谈
说来惭愧,也不怕你们笑话。做开发8年多,到目前还是一名不折不扣的扫地僧。年前的辞职,到现在还在家静养中。其实也没什么,就是回家总结一下自己这些年来在外工作与面试等做一个简单的总结与反思。做一下自己后面一个人生规划。不过在家每天也是在撸码,还有就是复习与学习现在的架构知识点,学习使人进步嘛!不是么?毕竟技术还是不能落下。
烂猪皮
2019/05/13
7030
金三银四面试回来,我想跟程序员们谈谈
web前端面试技巧-如何自我介绍?如何应对hr?
技术老大对你的印象与能力的认可,直接决定你是否能拿到offer,所以在技术老大面前多聊技术,别谈工作时间薪资待遇那些,那是人事面的流程。
肥晨
2023/04/04
1.3K0
推荐阅读
相关推荐
我看懂了,大受震撼!看大佬一年经验横扫各大offer
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验