多媒体开发之cgo

news2024/10/6 1:38:04

     go语言作为近十年来优秀的现代开发语言的代表,由于继承了c语言的简洁和很多现代语言的表达方式,在广泛的应用场景中得到众多爱好者的喜爱,如何将go和c、c++进行联合开发,拓展整个开发生态,不用重复造轮子,掌握cgo可以让你得心应手的在c和go之间传递信息,打通任督二脉。

    go在流媒体传输领域也有很强大的生态和优秀的轮子,比起传统的ffmpeg这种大而全的库,可以选择性的用一些小巧强悍的go语言写的库来替代ffmpeg,比如rtsp拉流,笔者用ffmpeg在android下写了一个推拉流的播放器,但是由于ffmpeg自成体系,在灵活定制方面有一些局限性,于是尝试用go rtsp来代替ffmpeg的rtsp拉流。

    首先我们需要利用cgo的交叉编译特性封装一个可以被c/c++调用的动态库,其中用到了cgo 进行设置和回调函数,并传递了类的指针,从而实现了c++ class的特性,达到了面向对象多路连接的设计目标。以下是go写的动态库源码,由于go的包管理做的特别棒,你可以用很少的代码实现一个多路拉流的应用。感觉比c++爽多了。

 

 

package main

import (
	"fmt"
	"sync"
	"time"
	"unsafe"

	"github.com/deepch/vdk/av"
	"github.com/deepch/vdk/codec/h264parser"
	"github.com/deepch/vdk/format/rtsp"
)

/*
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#cgo CFLAGS: -I.
void OnSendPacket(void * callclass,unsigned char *data, int len,int mediatype);
typedef void (*CallbackFunc)(unsigned char *,int,int);
*/
import "C"

// 导出的回调函数类型
// type
// void SendcallbackFunc(unsigned char* data, int length);
// extern CallbackFunc _callback;
//
//	static void setCallbackWrapper(CallbackFunc callback) {
//	    _callback = callback;
//	}
type CallbackFunc func(*byte, int, int)

// 接口定义
type MediaInterface interface {
	SetCallback(callback CallbackFunc)
}

// 结构体实现接口
type MediaImplementation struct {
	id       int
	callback CallbackFunc
	// C._callback
	callclass unsafe.Pointer // 修改为unsafe.Pointer类型
}

var (
	instances     = make(map[int]*MediaImplementation)
	instancesLock sync.Mutex
	callbackLock  sync.Mutex
)

func (m *MediaImplementation) SendData(data []byte, length int, mediatype int) {
	// if C._callback != nil {
	fmt.Println("callback data len", length, mediatype)
	// arr := (*C.uchar)(unsafe.Pointer(&data[0]))
	// C.OnSendPacket(arr, C.int(length), C.int(mediatype))
}

func (m *MediaImplementation) CallBackData(data *byte, length int, mediatype int) {
	goSlice := (*[1 << 30]byte)(unsafe.Pointer(data))[:length:length]
	m.SendData(goSlice, length, mediatype)
}

func (m *MediaImplementation) RtspClientStart(uri string) {
	// m.rtsp = unsafe.Pointer(&Rtspclient{
	// 	callback: m.CallBackData,
	// })
	// ((*Rtspclient)(m.rtsp)).rtspclient(uri)
	m.rtspConsumer(uri)
}

func (m *MediaImplementation) SetCallback(callback CallbackFunc) {
	m.callback = callback
}

//export CallRtspClientStart
func CallRtspClientStart(impl unsafe.Pointer, data *C.char) {
	if impl == nil {
		fmt.Println("Invalid implementation")
		return
	}
	m := (*MediaImplementation)(impl)
	m.rtspConsumer(C.GoString(data))
	// m.RtspClientStart(C.GoString(data))
}

//export NewMediaImplementation
func NewMediaImplementation() uintptr {
	instancesLock.Lock()
	defer instancesLock.Unlock()

	id := len(instances) + 1

	instance := &MediaImplementation{
		id: id,
	}

	instances[id] = instance

	return uintptr(unsafe.Pointer(instance))
}

//export GetMediaInstanceByID
func GetMediaInstanceByID(id C.int) uintptr { // 修改返回类型为uintptr
	instancesLock.Lock()
	defer instancesLock.Unlock()

	if instance, ok := instances[int(id)]; ok {
		return uintptr(unsafe.Pointer(instance))
	}

	return uintptr(0) // 返回表示未找到实例
}

//export CallSendData
func CallSendData(impl unsafe.Pointer, data *C.uchar, length C.int, mediatype C.int) {
	if impl == nil {
		fmt.Println("Invalid implementation")
		return
	}

	goSlice := (*[1 << 30]byte)(unsafe.Pointer(data))[:length:length]
	m := (*MediaImplementation)(impl)
	go m.SendData(goSlice, int(length), int(mediatype))
}

//export SetCallbackWrapper
func SetCallbackWrapper(impl unsafe.Pointer, callback unsafe.Pointer) {
	if impl == nil {
		fmt.Println("Invalid implementation")
		return
	}
	m := (*MediaImplementation)(impl)
	m.callclass = callback
	fmt.Println("SetCallbackWrapper callback", m.callback, callback)
	// C._callback = callbackFunc
	// m.SetCallback(callbackFunc)
}

func main() {}

func (m *MediaImplementation) rtspConsumer(uri string) {
	annexbNALUStartCode := func() []byte { return []byte{0x00, 0x00, 0x00, 0x01} }
	//fmt.Println("rtspConsumer starting...")

	session, err := rtsp.DialTimeout(uri, 10*time.Second)
	// defer session.Close()
	if err != nil {
		fmt.Errorf("rtsp Dial Error: %v", err)
		return
		//panic(err)
	}

	//session.RtpKeepAliveTimeout = 10 * time.Second
	session.RtpTimeout = 20 * time.Second
	session.RtpKeepAliveTimeout = 5 * time.Second
	session.RtspTimeout = 20 * time.Second
	codecs, err := session.Streams()
	if err != nil {
		fmt.Errorf("stream error: %v", err)
		// continue
		//panic(err)
	}
	for i, t := range codecs {
		fmt.Printf("Stream", i, "is of type", t.Type().String())
	}

	if codecs[0].Type() != av.H264 {
		//fmt.Println("RTSP feed must begin with a H264 codec")
		// continue
		//panic("RTSP feed must begin with a H264 codec")
	}
	if len(codecs) != 1 {
		//fmt.Println("Ignoring all but the first stream.")
	}

	// var previousTime time.Duration
	for {
		// select {
		// case <-rtspsrcch:
		// default:
		// if KVMrtsp.BInUse != true {
		// 	break
		// }
		pkt, err := session.ReadPacket()
		if err != nil {
			break
		}

		// if pkt.Idx != 0 {
		// 	//audio or other stream, skip it
		// 	continue
		// }
		if codecs[pkt.Idx].Type().IsVideo() {
			pkt.Data = pkt.Data[4:]

			// For every key-frame pre-pend the SPS and PPS
			if pkt.IsKeyFrame {
				pkt.Data = append(annexbNALUStartCode(), pkt.Data...)
				pkt.Data = append(codecs[0].(h264parser.CodecData).PPS(), pkt.Data...)
				pkt.Data = append(annexbNALUStartCode(), pkt.Data...)
				pkt.Data = append(codecs[0].(h264parser.CodecData).SPS(), pkt.Data...)
				pkt.Data = append(annexbNALUStartCode(), pkt.Data...)
			} else {
				pkt.Data = append(annexbNALUStartCode(), pkt.Data...)
			}

			// bufferDuration := pkt.Time - previousTime
			// previousTime = pkt.Time
			// m.CallBackData(pkt.Data,len(pkt.Data))
			fmt.Println("Is Video IsKeyFrame", pkt.IsKeyFrame, codecs[pkt.Idx].Type(), pkt.Time)
			mediatype := 0
			switch codecs[pkt.Idx].Type() {
			case av.H264:
				mediatype = 1
			case av.H265:
				mediatype = 2
			}
			length := len(pkt.Data)
			arr := (*C.uchar)(unsafe.Pointer(&pkt.Data[0]))
			C.OnSendPacket(m.callclass, arr, C.int(length), C.int(mediatype))
			// if err = KVMrtsp.Track.WriteSample(media.Sample{Data: pkt.Data, Duration: bufferDuration}); err != nil && err != io.ErrClosedPipe {
			// 	logger.Errorf("WriteSample error %v", err)
			// 	break
			// 	//panic(err)
			// }
		} else if codecs[pkt.Idx].Type().IsAudio() {
			// codecs, err = session.Streams()
			// if err != nil {
			// 	fmt.Println("Error getting streams")
			// 	break
			// }
			codec := codecs[pkt.Idx].(av.AudioCodecData)
			duration, err := codec.PacketDuration(pkt.Data)
			if err != nil {
				fmt.Println("Failed to get duration for audio:", err)
				break
			}
			fmt.Println("IS Audio Packet duration:", duration)
			mediatype := 0
			switch codecs[pkt.Idx].Type() {
			case av.AAC:
				mediatype = 3
			case av.PCM_MULAW:
				mediatype = 4
			case av.PCM_ALAW:
				mediatype = 5
			case av.SPEEX:
				mediatype = 6
			case av.NELLYMOSER:
				mediatype = 7
			case av.PCM:
				mediatype = 8
			case av.OPUS:
				mediatype = 9
			}
			length := len(pkt.Data)
			arr := (*C.uchar)(unsafe.Pointer(&pkt.Data[0]))
			C.OnSendPacket(m.callclass, arr, C.int(length), C.int(mediatype))
			// m.CallBackData(pkt.Data,len(pkt.Data))
			// err = audioTrack.WriteSample(media.Sample{Data: pkt.Data, Duration: duration})
			// if err != nil {
			// 	//fmt.Println("Failed to write audio sample", err)
			// 	break
			// }
		}
	}

	if err = session.Close(); err != nil {
		fmt.Errorf("session Close error %v", err)
		return
	}

	// time.Sleep(5 * time.Second)

	// }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/752042.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s 持久化存储

我们继续来查看 k8s 的卷&#xff0c;上一次我们分享了将磁盘挂载到容器中&#xff0c;empyDir 和 gitRepo 都是会随着 pod 的启动而创建&#xff0c;随着 pod 的删除而销毁 那么我们或许会有这样的需求&#xff0c;期望在 pod 上面读取节点的文件或者使用节点的文件系统来访问…

Win10 配置ADB安装2023.7.12版本

目录 1. ADB工具介绍2. ADB安装流程 参考 Win10 配置安装ADB教程总结20200514&#xff0c; fastboot刷机 1. ADB工具介绍 ADB ( Android Debug Bridge)&#xff0c;即Android 调试桥。是 Google 为开发人员提供的一种命令行工具&#xff0c;用于与安卓设备进行通信&#xff0…

开源网安加入东莞市大数据协会,共建安全可靠软件产业生态

​近日&#xff0c;开源网安成为东莞市大数据协会会员单位&#xff0c;与协会共同构建安全可靠软件产业生态&#xff0c;在科技创新、共建安全生态等方面发力&#xff0c;推动软件产业引领经济高质量发展&#xff0c;推动大湾区企业加快数字化建设进程。 东莞市大数据协会致力于…

Oracle使用PL/SQL导出表,结果显示insert语句

导出表到sql文件中有两种方法&#xff0c;下面我们列举两种方法的操作 1、选择工具->导出->选中要导出的表->点击sql插入->自定义导出路径以及文件&#xff0c;点击导出即可。当然也可以在where子句中增加条件&#xff0c;以便筛选导出 2、首选查询表数据&#xff…

阿里云无影云电脑使用方法_3分钟上手教程

阿里云无影云电脑即无影云电脑&#xff0c;云电脑如何使用&#xff1f;云电脑购买后没有用户名和密码&#xff0c;先创建用户设置密码&#xff0c;才可以登录连接到云电脑。云桌面想要访问公网还需要开通互联网访问功能。阿里云百科来详细说下阿里云无影云电脑从购买、创建用户…

Low-Light Image Enhancement via Stage-Transformer-Guided Network 论文阅读笔记

这是TCSVT 2023年的一篇暗图增强的论文 文章的核心思想是&#xff0c;暗图有多种降质因素&#xff0c;单一stage的model难以实现多降质因素的去除&#xff0c;因此需要一个multi-stage的model&#xff0c;文章中设置了4个stage。同时提出了用预设query向量来代表不同的降质因素…

Kubernetes 组件介绍

Kubernetes 组件 部署完 Kubernetes&#xff0c;便拥有了一个完整的集群 一组工作机器&#xff0c;称为节点&#xff0c; 会运行容器化应用程序。每个集群至少有一个工作节点 工作节点会托管 Pod &#xff0c;而 Pod 就是作为应用负载的组件。 控制平面管理集群中的工作节点…

【JAVA8】Stream\Comparator

Stream Reduction, 给N个数值&#xff0c;求出其总和/最大值/最小值/均值这一类的操作&#xff0c;称为Reduction Option Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true&#xff0c;调用get()方法会返回该对象。 Optional 类的引入很好的解…

<网络>UPD协议详解

UDP协议详解 网络传输的实质UDP协议端格式UDP的特点面向数据报UDP的缓冲区UDP使用注意事项基于UDP的应用层协议 网络传输的实质 在网络中&#xff0c;我们通过调用系统函数send/recv/write/read使用套接字来进行主机之间的网络通信&#xff0c;那么是不是我们在调用这几个函数…

vue3使用provideinject实现祖孙组件通讯

vue3使用provide&inject实现祖孙组件通讯 之前在使用vue2时&#xff0c;写过一篇博客记录父子组件的数据通讯 简单来说&#xff0c;父传子通过自定义属性&#xff0c;子传父通过自定义事件&#xff0c;兄弟间通过事件总线。也有更直观的vuex实现任意组件之间的数据通讯&a…

掘金量化—Python SDK文档—2.策略程序架构

目录 Python SDK文档 2.策略程序架构 2.1掘金策略程序初始化 2.2行情事件处理函数 2.3交易事件处理函数 2.4其他事件处理函数 2.5策略入口 Python SDK文档 2.策略程序架构 2.1掘金策略程序初始化 通过init 函数初始化策略,策略启动即会自动执行。在 init 函数中可以&#x…

数据结构与算法——什么是线性表(线性存储结构)

我们知道&#xff0c;具有“一对一”逻辑关系的数据&#xff0c;最佳的存储方式是使用线性表。那么&#xff0c;什么是线性表呢&#xff1f; 线性表&#xff0c;全名为线性存储结构。使用线性表存储数据的方式可以这样理解&#xff0c;即“把所有数据用一根线儿串起来&#xf…

【Unity面试篇】Unity 面试题总结甄选 |Unity基础篇 | ❤️持续更新❤️

2.2 前言 关于Unity面试题相关的所有知识点&#xff1a;&#x1f431;‍&#x1f3cd;2023年Unity面试题大全&#xff0c;共十万字面试题总结【收藏一篇足够面试&#xff0c;持续更新】为了方便大家可以重点复习某个模块&#xff0c;所以将各方面的知识点进行了拆分并更新整理…

swin-transformer

面向视觉任务的transfomer Vision Transformer(ViT)在视觉任务中的局限性 需求数据量巨大 CNN中是图像整体输入&#xff0c;并且经过多年的演变&#xff0c;发展出了多个不同的优化策略。从而在学习时能够在一定先验知识的前提下拟合数据。 而transformer是将图像切割成若干较小…

精彩回放 | AI驱动下的流程挖掘如何提升企业决策和运营效率?

流程挖掘是一种从事件日志中发现、监控和优化实际业务流程的技术。在AI的驱动下&#xff0c;流程挖掘能进行更深层次的自动化和智能化处理&#xff0c;从而帮助企业更准确地了解和优化业务流程&#xff0c;提高决策的精确度和运营的效率。然而&#xff0c;AI驱动的流程挖掘在实…

FPGA——pwm呼吸灯

文章目录 一、实验环境二、实验任务三、实验过程3.1 verilog代码3.2 引脚配置 四、仿真4.1 仿真代码4.2 仿真结果 五、实验结果六、总结 一、实验环境 quartus 18.1 modelsim vscode Cyclone IV开发板 二、实验任务 呼吸灯是指灯光在微电脑的控制之下完成由亮到暗的逐渐变化…

便捷查物流教程

当下寄递物品早已成为常态&#xff0c;而如何快速进行物流信息查询&#xff0c;成为收寄人所关心的问题。在回答这个问题之前&#xff0c;首先我们要知道&#xff0c;物流信息查询&#xff0c;有哪些方法&#xff1f; 1、官网单号查询 知道快递公司和单号的情况下&#xff0c;…

目标检测——目标检测概述

目录 目标检测常用的开源数据集PASCAL VOC数据集MS COCO数据集 常用的评价指标IOU&#xff08;交并比&#xff09;mAP&#xff08;Mean Average Precision&#xff09; NMS&#xff08;非极大值抑制&#xff09;目标检测方法分类 目标检测 常用的开源数据集 PASCAL VOC数据集 …

遭遇勒索攻击,日本名古屋港停摆两天!

日前&#xff0c;中央社东京报道一则勒索软件讯息。日本名古屋港的货柜码头遭受勒索病毒攻击后发生系统故障&#xff0c;系统数据已被加密&#xff0c;无法装卸货柜&#xff0c;造成港内5处货柜码头全数停摆长达两天&#xff0c;造成了巨额损失。 名古屋港是日本汽车产业聚集的…

ArcGIS如何制作横版图例

如果你经常制图&#xff0c;肯定使用过插入图例这个功能&#xff0c;默认情况下&#xff0c;插入的图例是竖着的&#xff0c;在某些情况下&#xff0c;如果需要横着的图例是否可以实现呢&#xff0c;答案是肯定的&#xff0c;这里为大家介绍一下ArcGIS如何制作横版图例&#xf…