关于Nodejs通过ffi调用多线程库
关于Nodejs通过ffi调用多线程库
遇到个问题,哪位大牛给个解决方法 应用:nodejs 通过 ffi调用 c库,c库为多播放器(多线程库) 贴一下部分代码: var dtplib =ffi.Library(‘vendor/linux_x64/libdtp’, { “player_register_all”:[‘void’,[]], “dtplayer_init”:[‘pointer’,[dtp_para_ptr]], “dtplayer_start”:[‘int’,[voidptr]], } );
var dtp_cb = ffi.Callback(‘void’,[dtp_state_ptr],function(state) { }); function dtplayer§ { var para = p; var priv; if(!para.update_cb) para.update_cb = dtp_cb;
this.bindEvents();
events.EventEmitter.call(this);
console.log('[node-js] : dtplayer constructer');
}
dtplayer.prototype.play = function()
{
var self = this;
dtplib.player_register_all();
priv = dtplib.dtplayer_init(para.ref());
dtplib.dtplayer_start(priv);
this.emit(‘playing’);
}
dtplayer.prototype.bindEvents = function()
{
this.on(‘playing’,function(){console.log(“receive playing signal”)});
this.on(‘update_info’,function(state)
{
console.log(“cur time:” + state.deref().cur_time);
}
);
}
这里调用start启动播放后,播放器内部会启动多个线程进行解码播放,但上层play执行完毕后就退出了
这里有个函数指针 dtp_cb传递进去,经过测试可以调用到,但dtp_cb并不属于dtplayer这个类,因此不能通过this.emit等定时触发回调
完整代码:https://github.com/peterfuture/node-dtplayer/blob/node-demo/libs/dtplayer.js
要通过 Node.js 的 ffi
库调用一个多线程的 C 库,并且希望在 JavaScript 层处理这些多线程产生的事件,需要特别注意如何管理和触发这些回调。以下是一个简化的示例来说明如何实现这一点。
示例代码
首先,我们需要定义一些必要的类型和函数:
const ffi = require('ffi-napi');
const ref = require('ref-napi');
// 定义一些类型
const voidPtr = ref.refType('void');
const int = 'int';
const voidFunc = 'void';
// 定义 C 库中的函数签名
const lib = ffi.Library('vendor/linux_x64/libdtp', {
'player_register_all': [voidFunc, []],
'dtplayer_init': ['pointer', ['pointer']], // 返回一个指针
'dtplayer_start': [int, [voidPtr]], // 接受一个指针作为参数
'register_callback': [voidFunc, [voidPtr]] // 注册回调函数
});
// 定义回调函数类型
const callbackType = ffi.Function(voidFunc, [voidPtr]);
// 创建一个播放器类
class DTPPlayer {
constructor(params) {
this.params = params;
this.callback = null;
// 初始化播放器并注册回调
this.handle = lib.dtplayer_init(this.params.ref());
this.registerCallback();
}
registerCallback() {
const self = this;
this.callback = new callbackType(function(state) {
console.log("Callback triggered with state:", state);
if (self.emit) {
self.emit('update_info', state);
}
});
lib.register_callback(this.callback.address());
}
play() {
lib.player_register_all();
lib.dtplayer_start(this.handle);
// 播放开始后,事件循环继续运行
process.nextTick(() => {
console.log("Playback started");
});
}
bindEvents() {
this.on('playing', () => {
console.log("Receive playing signal");
});
this.on('update_info', (state) => {
console.log("Current time:", state.deref().cur_time);
});
}
on(event, listener) {
if (!this.listeners) {
this.listeners = {};
}
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
emit(event, data) {
if (this.listeners && this.listeners[event]) {
this.listeners[event].forEach(listener => listener(data));
}
}
}
// 使用示例
const params = {
update_cb: null
};
const player = new DTPPlayer(params);
player.play();
player.bindEvents();
解释
-
类型定义:
ffi.Library
中定义了 C 库中的函数及其参数类型。callbackType
定义了一个函数指针类型,用于接收 C 库中的回调函数。
-
DTPPlayer 类:
- 构造函数初始化播放器并注册回调函数。
registerCallback
方法创建并注册回调函数,该回调函数将捕获来自 C 库的数据并在 JavaScript 层触发事件。play
方法启动播放并确保事件循环不会立即退出。bindEvents
方法绑定事件监听器。on
和emit
方法用于管理事件系统。
-
事件处理:
- 在
registerCallback
中,回调函数被设置为在 C 库中触发时调用,并在 JavaScript 层触发相应的事件。
- 在
这样,即使 C 库在后台启动多个线程,你也可以在 JavaScript 层正确地处理这些线程产生的事件。
https://github.com/rbranson/node-ffi/wiki/Node-FFI-Tutorial#async-library-calls
這個教程看來有幫助 ?
<pre> dtplib.dtplayer_start.async(???, function (err, res) { //empty });
dtplib.dtplayer_start(priv, function callback(err, res) { if (err) throw new Error(“oops”) //code here… }); </pre>
不太清楚你的意思. codes on github? <pre> dtplib.dtplayer_start(priv, function callback(err, res) { if (err) throw new Error(“oops”) dtplib.dtplayer_pause() // Pause after start callback fired }); </pre>
沒用過這個library 以下只是猜測…
我想 <pre> this.priv = dtplib.dtplayer_init(this.para.ref()); dtplib.dtplayer_start(this.priv); </pre> 這兩句 把dtp_cb() 和 dtplayer_start() 勾起來 觸發數次 後面退出 應該是都播放完了吧? 你可以用async() 掛一個 onEnd Handler() 驗證一下 <pre><code> dtplib.dtplayer_start.async(???, function (err, res) { //empty });
dtplib.dtplayer_start(priv, function callback(err, res) { if (err) throw new Error(“oops”) console.log(“player end”);. }); </code></pre>
這兩句 把dtp_cb() 和 dtplayer_start() 勾起來 觸發數次 後面退出 re: 是的,启动后在dtp_cb是可以通过emit触发 dtplayer类监听的 events
你可以用async() 掛一個 onEnd Handler() 驗證一下 re: 这个具体是什么意思,我之前是这样写的 dtplib.dtplayer_start.async(priv, function(err,res){ console.log(‘play_end’); }); 但你上面 两个 dtplib.dtplayer_start.async(???, function (err, res) 。。。 dtplib.dtplayer_start(priv, function callback(err, res) {。。。 我不太清楚,需要这样定义两次吗 还有一旦用了async ,是需要一直播放到结束,期间你再调用其他函数,是不会响应的吧
能提供一個網址讓我測試嗎? 我想從GitHub上clone 來測試 但我不知道應該填什麼url在 player.js?
https://github.com/peterfuture/node-dtplayer 最新的更新已经可以跑起来了 但由于本身需要依赖ffmpeg和 alsa, 因此可能跑不起来不大容易 简单步骤如下 1 install alsa & ffmpeg 2 git clone https://github.com/peterfuture/node-dtplayer 3 test: node example/player.js url (url是与音频文件的本地目录) 说明:目前只支持Linux系统 和 audio , 稍后会支持video 若跑不起来,请告诉错误,谢谢!
node-dtplayer的目标是做一个nodejs封装的播放器: 同时支持 在线视频 和 本地视频
根据你的描述,你在使用 Node.js 的 ffi
库调用 C 库时遇到了多线程播放器的问题。当调用 dtplayer_start
函数启动播放器后,播放器会在多个线程中运行,但是由于 JavaScript 的事件循环特性,一旦 play
方法执行完毕,Node.js 进程可能会提前结束。
为了解决这个问题,你需要确保 Node.js 进程在播放完成之前不会退出。可以通过以下方式实现:
- 使用异步操作保持进程运行:例如,在播放结束后手动保持进程运行。
- 修改 C 库回调机制:如果 C 库支持回调,可以通过 JavaScript 回调函数与 Node.js 进行通信。
示例代码
修改后的 JavaScript 代码
var ffi = require('ffi-napi');
var ref = require('ref-napi');
var StructType = require('ref-struct-napi');
// 假设 dtp_para_ptr 和 dtp_state_ptr 是结构体定义
var dtp_para_ptr = StructType({ /* 结构体定义 */ });
var dtp_state_ptr = StructType({ /* 结构体定义 */ });
var voidptr = ref.refType('void');
var dtplib = ffi.Library('vendor/linux_x64/libdtp',
{
"player_register_all": ['void', []],
"dtplayer_init": ['pointer', [dtp_para_ptr]],
"dtplayer_start": ['int', [voidptr]],
});
var dtp_cb = ffi.Callback('void', [dtp_state_ptr], function(state) {
console.log("Current time:", state.cur_time);
});
function dtplayer(para) {
if (!para.update_cb)
para.update_cb = dtp_cb;
this.bindEvents();
events.EventEmitter.call(this);
console.log('[node-js] : dtplayer constructor');
}
dtplayer.prototype.play = function() {
var self = this;
dtplib.player_register_all();
var priv = dtplib.dtplayer_init(para.ref());
dtplib.dtplayer_start(priv);
// 确保进程在播放完成后不退出
setTimeout(() => {}, Number.MAX_SAFE_INTEGER);
}
dtplayer.prototype.bindEvents = function() {
this.on('playing', () => {
console.log("Receive playing signal");
});
this.on('update_info', (state) => {
console.log("Current time:", state.deref().cur_time);
});
}
module.exports = dtplayer;
解释
- 异步操作:
setTimeout(() => {}, Number.MAX_SAFE_INTEGER);
用于确保 Node.js 进程在播放完成后不会立即退出。 - C 库回调:通过
ffi.Callback
定义回调函数dtp_cb
并将其传递给 C 库,这样可以在播放过程中接收到更新信息并处理。
这种方法可以确保 Node.js 进程在播放完成后仍然保持运行状态,从而避免进程过早退出的问题。