关于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


9 回复

要通过 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();

解释

  1. 类型定义:

    • ffi.Library 中定义了 C 库中的函数及其参数类型。
    • callbackType 定义了一个函数指针类型,用于接收 C 库中的回调函数。
  2. DTPPlayer 类:

    • 构造函数初始化播放器并注册回调函数。
    • registerCallback 方法创建并注册回调函数,该回调函数将捕获来自 C 库的数据并在 JavaScript 层触发事件。
    • play 方法启动播放并确保事件循环不会立即退出。
    • bindEvents 方法绑定事件监听器。
    • onemit 方法用于管理事件系统。
  3. 事件处理:

    • 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 进程在播放完成之前不会退出。可以通过以下方式实现:

  1. 使用异步操作保持进程运行:例如,在播放结束后手动保持进程运行。
  2. 修改 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;

解释

  1. 异步操作setTimeout(() => {}, Number.MAX_SAFE_INTEGER); 用于确保 Node.js 进程在播放完成后不会立即退出。
  2. C 库回调:通过 ffi.Callback 定义回调函数 dtp_cb 并将其传递给 C 库,这样可以在播放过程中接收到更新信息并处理。

这种方法可以确保 Node.js 进程在播放完成后仍然保持运行状态,从而避免进程过早退出的问题。

回到顶部