2023-03-09:用golang调用ffmpeg,将流媒体数据(以RTMP为例)保存成本地文件(以flv为例)。

2023-05-16

2023-03-09:用golang调用ffmpeg,将流媒体数据(以RTMP为例)保存成本地文件(以flv为例)。

答案2023-03-09:

这是最简单的收流器。本文记录一个最简单的基于FFmpeg的收流器。收流器和推流器的作用正好相反:推流器用于将本地文件以流媒体的形式发送出去,而收流器用于将流媒体内容保存为本地文件。

本文记录的推流器可以将RTMP流媒体保存成为一个本地的FLV文件。由于FFmpeg本身支持很多的流媒体协议和封装格式,所以也支持其它的封装格式和流媒体协议。

使用 github.com/moonfdd/ffmpeg-go 库,收流器的代码写在了这个库里,基于雷霄骅的代码修改。

需要修改代码里的rtmp地址,不然程序会报错。

一、先启动lalserver。lal是go语言开源的流媒体服务器。
二、执行命令:

go run ./examples/leixiaohua1020/simplest_ffmpeg_streamer/main.go
go run ./examples/leixiaohua1020/simplest_ffmpeg_receiver/main.go
./lib/ffplay ./out/receive.flv

参考了雷霄骅的基于libx265的视频编码器,代码用golang编写。代码如下:

// https://github.com/leixiaohua1020/simplest_ffmpeg_streamer/blob/master/simplest_ffmpeg_receiver/simplest_ffmpeg_receiver.cpp
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"
)

// '1': Use H.264 Bitstream Filter
const USE_H264BSF = 0

func main0() (ret ffcommon.FInt) {
	var ofmt *libavformat.AVOutputFormat
	//Input AVFormatContext and Output AVFormatContext
	var ifmt_ctx, ofmt_ctx *libavformat.AVFormatContext
	var pkt libavcodec.AVPacket
	var in_filename, out_filename string
	var i ffcommon.FInt
	var videoindex ffcommon.FInt = -1
	var frame_index ffcommon.FInt = 0
	var h264bsfc *libavcodec.AVBitStreamFilterContext
	in_filename = "rtmp://localhost/publishlive/livestream"
	//in_filename  = "rtp://233.233.233.233:6666";
	//out_filename = "receive.ts";
	//out_filename = "receive.mkv";
	out_filename = "./out/receive.flv"

	libavformat.AvRegisterAll()
	//Network
	libavformat.AvformatNetworkInit()
	//Input
	ret = libavformat.AvformatOpenInput(&ifmt_ctx, in_filename, nil, nil)
	if ret < 0 {
		fmt.Printf("Could not open input file.")
		goto end
	}
	ret = ifmt_ctx.AvformatFindStreamInfo(nil)
	if ret < 0 {
		fmt.Printf("Failed to retrieve input stream information")
		goto end
	}

	for i = 0; i < int32(ifmt_ctx.NbStreams); i++ {
		if ifmt_ctx.GetStream(uint32(i)).Codec.CodecType == libavutil.AVMEDIA_TYPE_VIDEO {
			videoindex = i
			break
		}
	}

	ifmt_ctx.AvDumpFormat(0, in_filename, 0)

	//Output
	libavformat.AvformatAllocOutputContext2(&ofmt_ctx, nil, "", out_filename) //RTMP

	if ofmt_ctx == nil {
		fmt.Printf("Could not create output context\n")
		ret = libavutil.AVERROR_UNKNOWN
		goto end
	}
	ofmt = ofmt_ctx.Oformat
	for i = 0; i < int32(ifmt_ctx.NbStreams); i++ {
		//Create output AVStream according to input AVStream
		in_stream := ifmt_ctx.GetStream(uint32(i))
		out_stream := ofmt_ctx.AvformatNewStream(in_stream.Codec.Codec)
		if out_stream == nil {
			fmt.Printf("Failed allocating output stream\n")
			ret = libavutil.AVERROR_UNKNOWN
			goto end
		}
		//Copy the settings of AVCodecContext
		ret = libavcodec.AvcodecCopyContext(out_stream.Codec, in_stream.Codec)
		if ret < 0 {
			fmt.Printf("Failed to copy context from input to output stream codec context\n")
			goto end
		}
		out_stream.Codec.CodecTag = 0
		if ofmt_ctx.Oformat.Flags&libavformat.AVFMT_GLOBALHEADER != 0 {
			out_stream.Codec.Flags |= libavcodec.AV_CODEC_FLAG_GLOBAL_HEADER
		}
	}
	//Dump Format------------------
	ofmt_ctx.AvDumpFormat(0, out_filename, 1)
	//Open output URL
	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 URL '%s'", out_filename)
			goto end
		}
	}
	//Write file header
	ret = ofmt_ctx.AvformatWriteHeader(nil)
	if ret < 0 {
		fmt.Printf("Error occurred when opening output URL\n")
		goto end
	}

	if USE_H264BSF != 0 {
		h264bsfc = libavcodec.AvBitstreamFilterInit("h264_mp4toannexb")
	}

	for {
		var in_stream, out_stream *libavformat.AVStream
		//Get an AVPacket
		ret = ifmt_ctx.AvReadFrame(&pkt)
		if ret < 0 {
			break
		}

		in_stream = ifmt_ctx.GetStream(pkt.StreamIndex)
		out_stream = ofmt_ctx.GetStream(pkt.StreamIndex)
		/* copy packet */
		//Convert PTS/DTS
		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
		//Print to Screen
		if pkt.StreamIndex == uint32(videoindex) {
			fmt.Printf("Receive %8d video frames from input URL\n", frame_index)
			frame_index++

			if USE_H264BSF != 0 {
				h264bsfc.AvBitstreamFilterFilter(in_stream.Codec, "", &pkt.Data, (*int32)(unsafe.Pointer(&pkt.Size)), pkt.Data, int32(pkt.Size), 0)
			}
		}
		//ret = av_write_frame(ofmt_ctx, &pkt);
		ret = ofmt_ctx.AvInterleavedWriteFrame(&pkt)

		if ret < 0 {
			fmt.Printf("Error muxing packet\n")
			break
		}

		pkt.AvFreePacket()

	}

	if USE_H264BSF != 0 {
		h264bsfc.AvBitstreamFilterClose()
	}

	//Write file trailer
	ofmt_ctx.AvWriteTrailer()
end:
	libavformat.AvformatCloseInput(&ifmt_ctx)
	/* close output */
	if ofmt_ctx != nil && ofmt.Flags&libavformat.AVFMT_NOFILE == 0 {
		ofmt_ctx.Pb.AvioClose()
	}
	ofmt_ctx.AvformatFreeContext()
	if ret < 0 && ret != libavutil.AVERROR_EOF {
		fmt.Printf("Error occurred.\n")
		return -1
	}
	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
		}
	}

	// go func() {
	// 	time.Sleep(1000)
	// 	exec.Command("./lib/ffplay.exe", "rtmp://localhost/publishlive/livestream").Output()
	// 	if err != nil {
	// 		fmt.Println("play err = ", err)
	// 	}
	// }()

	main0()
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

2023-03-09:用golang调用ffmpeg,将流媒体数据(以RTMP为例)保存成本地文件(以flv为例)。 的相关文章

  • Video.js 使用教程 - 手把手教你基于 Vue 搭建 HTML 5 视频播放器

    本文首发 xff1a Video js 使用教程 手把手教你基于 Vue 搭建 HTML 5 视频播放器 Video js 是最强大的网页嵌入式 HTML 5 视频播放器的组件库之一 xff0c 也是大多数人首选的网页视频播放解决方案 复杂
  • ECharts 饼图颜色设置教程 - 4 种方式设置饼图颜色

    本文首发 xff1a ECharts 饼状图颜色设置教程 4 种方式设置饼图颜色 ECharts 饼状图颜色设置教程 方法一 xff1a 在 96 series 96 内配置饼状图颜色方法二 xff1a 在 96 option 96 内配置
  • 使用和风天气 API 10分钟搭建天气预报数据看板

    本文首发 xff1a 使用和风天气 API 10分钟搭建天气预报数据看板 使用和风天气 API 10分钟搭建天气预报数据看板 第 1 步 xff1a 注册和风天气 API 及卡拉云 xff08 1 xff09 注册和风天气 API xff0
  • 在 Vue3 + Element Plus 中生成动态表格,动态修改表格,多级表头,合并单元格

    本文完整版 xff1a 在 Vue3 43 Element Plus 中生成动态表格 xff0c 动态修改表格 xff0c 多级表头 xff0c 合并单元格 Vue3 43 Element Plus 生成动态表格 Vue3 43 Eleme
  • 后端实战手把手教你写文件上传接口:如何使用 Node.js + MongoDB 开发 RESTful API 接口(Node.js + Express + MongoDB)

    后端实战手把手教你写文件上传接口 文件上传管理后台 Node js 43 Express 43 Multer 43 MongoDB后端项目结构 配置 Node js 开发环境配置 MongoDB 数据库配置文件上传存储的中间件创建文件上传的
  • React Table 表格组件使用教程 排序、分页、搜索过滤筛选功能实战开发

    React Table 表格组件使用教程 react table 安装和使用React Table 表格排序功能React Table 表格搜索过滤筛选功能React Table 表格分页功能React table 排序 搜索过滤筛选 分页
  • React useReducer 终极使用教程

    React useReducer 终极使用教程 useReducer 工作原理关于 reducer 函数 懒惰创建初始 statedispatch 函数不触发dispatch useState 和 useReducer 比较和区别及应用场景
  • 简单的数据可视化---绘制散点图

    使用scatter绘制散点图并设置其样式 绘制单个点 要绘制单个点 xff0c 可以使用函数scatter xff0c 并向它传递一对x和y坐标 xff0c 它将在指定位置绘制一个点 xff1a span class token keywo
  • python随机漫步

    随机漫步 这次我们将使用python生成随机漫步数据 xff0c 然后用matplotlib的方式将这些数据呈现出来 随机漫步每次行走都完全是随机的 xff0c 没有明确的方向 xff0c 结果是由一系列随机决策决定的 你可以这样认为 xf
  • python基础

    python基础 文章目录 python基础基础语法关键字标识符多行语句数据类型 字符串输出import 与from import基本数据类型多个变量赋值标准数据类型Number xff08 数字 xff09 数值运算数学函数随机数函数三角
  • Debian简介与Debian源

    1 Deiban是一套自由操作系统 使用Linux内核 xff0c 大部分基本工具来自GNU计划 因此称Deiban为Debian GNU Linux Debian是第一个使用包管理系统的Linux发行版 xff0c 是的安装和删除软件变得
  • cloudmusic:网易云爬虫

    文章目录 cloudmuscic xff1a 网易云音乐爬虫安装使用music对象1 music对象属性2 music对象方法3 music对象函数 user对象1 user对象属性2 user对象方法3 获取user对象函数 cloudm
  • 链家深圳二手房房价数据分析

    文章目录 链家深圳二手房房价数据分析1 链家数据爬取源码2 雷达图的绘制2 1 源码2 2 雷达图效果图 3 饼状图的绘制3 1 源代码3 2 饼状图效果图 4 多维散点图4 1 源码4 2 多维散点图效果图 5 玫瑰图5 1 源码5 2
  • UNIX基础知识

    文章目录 UNIX基础知识1 1 引言1 2 UNIX体系结构1 3 登录1 4 文件和目录1 5 输入和输出1 6 程序和进程1 程序2 进程和进程ID3 进程控制4 线程和线程ID 1 7 出错处理出错恢复 1 8 用户标识1 用户ID
  • 栈和队列——小猫钓鱼

    星期天A和B在一起玩扑克牌 xff0c 他们在玩一个古怪的扑克牌游戏 小猫钓鱼 游戏的规则是这样的 xff0c 将一副扑克牌平均分成两份 xff0c 每人拿一份 A先拿出手中的第一章牌放在桌上 xff0c 然后B也从手里拿出一张牌放在桌上
  • 二叉树与二叉树遍历

    树的介绍 你可能回文树和图有什么区别 xff1f 这个称之为树的东西和无向图差不多嘛 树其实就是不包含回路的连通无向图 图画的不好啊 xff0c 把箭头忽略一下将就看一下 xff0c 上面这个图左边就是一棵树 xff0c 而右边就是一个图
  • 广度优先搜索

    在前面的迷宫中 xff0c 我们使用了深度优先搜索的方法 xff0c 这里介绍一个新的方法来解决这个问题 广度优先搜索 xff0c 也称为宽度优先搜索 这里还是用一个二维数组来存储迷宫 xff0c 最开始的时候A也是在迷宫 0 0 处 xf
  • 图的遍历--深度优先搜索

    深度优先搜索和广度优先搜索 xff0c 其实都是针对图的变量而言的 简单来说 xff0c 图就是一些圆点和连接这些圆点的直线组成 例如上图的这五个定点和四条边 我们现在从1号顶点开始遍历整个图 xff0c 遍历指的就是把图的每一个顶点都访问
  • 暴力的枚举

    枚举算法又叫穷举算法 xff0c 光听名字就是能知道这个很暴力 有一个题 xff1a 3 6528 61 3 8256 xff0c 在两个方框里面填入相同的数字使得等式成立 你可能会觉得这个很简单 xff0c 3行代码就可以搞定 xff1a
  • 虚拟机的使用及基本命令

    虚拟机的使用 kiosk 64 foundation0 Desktop rht vmctl view desktop 显示虚拟机 kiosk 64 foundation0 Desktop rht vmctl start desktop 打开

随机推荐