写点什么

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

  • 2023-04-27
    北京
  • 本文字数:5798 字

    阅读完需:约 19 分钟

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,将其复制到相应的输出文件中,并确保时间戳和持续时间等参数正确设置。

命令如下:

go run ./examples/internalexamples/remuxing/main.go ./resources/big_buck_bunny.mp4 ./out/remuxing.flv./lib/ffplay ./out/remuxing.flv
复制代码

golang 完整代码如下:

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()}
复制代码



发布于: 刚刚阅读数: 4
用户头像

公众号:福大大架构师每日一题 2021-02-15 加入

公众号:福大大架构师每日一题

评论

发布
暂无评论
2022-04-27:用go语言重写ffmpeg的remuxing.c示例。_golang_福大大架构师每日一题_InfoQ写作社区