首页 > 其他分享 >2022-04-27:用go语言重写ffmpeg的remuxing.c示例。

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

时间:2023-04-27 21:34:03浏览次数:43  
标签:libavutil 27 remuxing stream 示例 ctx ret pkt 04

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

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

标签:libavutil,27,remuxing,stream,示例,ctx,ret,pkt,04
From: https://blog.51cto.com/u_14891145/6232115

相关文章

  • 4.27打卡
     #include<bits/stdc++.h>usingnamespacestd;classTime{private:intminute;inthour;public:voidset(inth,intm){minute=m;hour=h;}friendintoperator-(Time,Time);};intoperator-(......
  • 2023.4.27
     1//实验六任务22#include<iostream>3#include<string>4usingnamespacestd;5classPeople6{7public:8People(){}9~People(){}10voidsetValue(intm,stringstr)11{12age=m;13name=str;......
  • 2023/4/27
    L1-007念数字分数 10全屏浏览题目作者 翁恺单位 浙江大学输入一个整数,输出每个数字对应的拼音。当整数为负数时,先输出fu字。十个数字对应的拼音如下:0:ling1:yi2:er3:san4:si5:wu6:liu7:qi8:ba9:jiu 输入格式:输入在一行中给......
  • 1048. 最长字符串链
    题目描述给了一个单子数组words给了字母前身的定义:A在任何地方加一个字符,凑成B,A就是B的前身问从words中怎么选,能构成最长的词链?f1-记忆化搜索基本分析怎么找到子问题?假如s是词链的最后一个单词,那么枚举去掉s某位后的构成新的词s-1,s-1就是s的更小一级的子问题dfs怎么实现?......
  • 4月27日打卡
    美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统。2014年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个正方形。现在你也跟他一起画吧!输入格式:输入在一行中给出正方形边长N(3≤N≤21)和......
  • 2023.4.27编程一小时打卡
    一、问题描述:建立一个向量容器的实例s,不断对s调用push_back向其中增加新的元素,观察在此过程中s.capacity()的变化。二、解题思路:首先,编写一个向量容器vector<int>s,利用循环对其进行不断调用push_back,再输出它的capacity()函数观察它向量容器的容量的变化。三、代码实现:1#in......
  • commitlint.config的配置和中文文档(附使用示例)
     https://blog.csdn.net/qq_38290251/article/details/111646491https://blog.csdn.net/qq_21197033/article/details/128609033 TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChineseSimpl......
  • nginx出现504错误的原因分析及解决
    线上环境使用的是nginx代理到后端应用(java),对应用进行了一次压测发现nginx出现大量的504代码,即网关超时(GatewayTime-out)错误。 原因分析:首先504是网关超时错误,通常是nginx将请求代理到后端应用时,后端应用没有在规定的时间返回数据,需要开发检查下应用那块有什么耗时的操作,比如:......
  • 4.27
    #include<stdio.h>voidprint(ints[]);intjudge(intc[]);intj=0;main(){intsweet[10]={10,2,8,22,16,4,10,6,14,20};inti,t[10],l;printf("child12345678910\n");printf("......................\n");printf("tim......
  • 每日总结2023-04-27——关于全局变量的基础使用
    今天完成了对全局变量的使用packagecom.example.math;/**全局变量**/importandroid.app.Application;publicclassCustomApplicationextendsApplication{privatestaticfinalStringVALUE="111";privateStringvalue;@Overridepublic......