luat_lib_rtmp.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /*
  2. @module rtmp
  3. @summary RTMP 直播推流
  4. @version 1.0
  5. @date 2025.12.8
  6. @tag LUAT_USE_RTMP
  7. @usage
  8. -- RTMP推流示例
  9. local rtmp = rtmp.create("rtmp://example.com:1935/live/stream")
  10. rtmp:setCallback(function(state, ...)
  11. if state == rtmp.STATE_CONNECTED then
  12. print("已连接到推流服务器")
  13. elseif state == rtmp.STATE_PUBLISHING then
  14. print("已开始推流")
  15. elseif state == rtmp.STATE_ERROR then
  16. print("出错:", ...)
  17. end
  18. end)
  19. rtmp:connect()
  20. -- 开始处理
  21. rtmp:start()
  22. -- 30秒后停止
  23. sys.wait(30000)
  24. rtmp:stop()
  25. -- 断开连接
  26. rtmp:disconnect()
  27. rtmp:destroy()
  28. */
  29. #include "luat_base.h"
  30. #include "luat_rtmp_push.h"
  31. #include "luat_msgbus.h"
  32. #include "luat_mem.h"
  33. #include "lauxlib.h"
  34. #include <stdlib.h>
  35. #include "lwip/timeouts.h"
  36. #include "lwip/tcpip.h"
  37. #define LUAT_LOG_TAG "rtmp"
  38. #include "luat_log.h"
  39. typedef struct {
  40. rtmp_ctx_t *rtmp;
  41. int callback_ref;
  42. } luat_rtmp_userdata_t;
  43. /**
  44. 创建RTMP推流上下文
  45. @api rtmp.create(url)
  46. @string url RTMP服务器地址, 格式: rtmp://host:port/app/stream
  47. @return userdata RTMP上下文对象
  48. @usage
  49. local rtmp = rtmp.create("rtmp://example.com:1935/live/stream")
  50. */
  51. static int l_rtmp_create(lua_State *L) {
  52. const char *url = luaL_checkstring(L, 1);
  53. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)lua_newuserdata(L, sizeof(luat_rtmp_userdata_t));
  54. if (!ud) {
  55. LLOGE("内存分配失败");
  56. lua_pushnil(L);
  57. return 1;
  58. }
  59. ud->rtmp = rtmp_create();
  60. if (!ud->rtmp) {
  61. LLOGE("RTMP上下文创建失败");
  62. lua_pushnil(L);
  63. return 1;
  64. }
  65. ud->rtmp->user_data = (void *)ud;
  66. ud->callback_ref = LUA_NOREF;
  67. if (rtmp_set_url(ud->rtmp, url) != 0) {
  68. LLOGE("RTMP URL设置失败");
  69. rtmp_destroy(ud->rtmp);
  70. lua_pushnil(L);
  71. return 1;
  72. }
  73. luaL_getmetatable(L, "rtmp_ctx");
  74. lua_setmetatable(L, -2);
  75. LLOGD("RTMP上下文创建成功: %s", url);
  76. return 1;
  77. }
  78. /**
  79. 设置RTMP状态回调函数
  80. @api rtmp:setCallback(func)
  81. @function func 回调函数, 参数为 (state, ...)
  82. @return nil 无返回值
  83. @usage
  84. rtmp:setCallback(function(state, ...)
  85. if state == rtmp.STATE_IDLE then
  86. print("空闲状态")
  87. elseif state == rtmp.STATE_CONNECTING then
  88. print("正在连接")
  89. elseif state == rtmp.STATE_HANDSHAKING then
  90. print("握手中")
  91. elseif state == rtmp.STATE_CONNECTED then
  92. print("已连接")
  93. elseif state == rtmp.STATE_PUBLISHING then
  94. print("推流中")
  95. elseif state == rtmp.STATE_DISCONNECTING then
  96. print("正在断开")
  97. elseif state == rtmp.STATE_ERROR then
  98. print("错误:", ...)
  99. end
  100. end)
  101. */
  102. static int l_rtmp_set_callback(lua_State *L) {
  103. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  104. if (!ud || !ud->rtmp) {
  105. lua_pushboolean(L, 0);
  106. return 1;
  107. }
  108. if (lua_isfunction(L, 2)) {
  109. if (ud->callback_ref != LUA_NOREF) {
  110. luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
  111. }
  112. lua_pushvalue(L, 2);
  113. ud->callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  114. LLOGD("RTMP回调函数已设置");
  115. } else if (lua_isnil(L, 2)) {
  116. if (ud->callback_ref != LUA_NOREF) {
  117. luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
  118. ud->callback_ref = LUA_NOREF;
  119. }
  120. LLOGD("RTMP回调函数已清除");
  121. } else {
  122. LLOGE("参数错误,需要function或nil");
  123. lua_pushboolean(L, 0);
  124. return 1;
  125. }
  126. lua_pushboolean(L, 1);
  127. return 1;
  128. }
  129. static int l_rtmp_handler(lua_State *L, void *udata) {
  130. rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
  131. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)msg->ptr;
  132. if (!ud || ud->callback_ref == LUA_NOREF) {
  133. return 0;
  134. }
  135. int state = msg->arg1;
  136. lua_rawgeti(L, LUA_REGISTRYINDEX, ud->callback_ref);
  137. if (lua_isfunction(L, -1)) {
  138. lua_pushinteger(L, state);
  139. lua_call(L, 1, 0);
  140. }
  141. return 0;
  142. }
  143. /**
  144. 状态回调函数(内部使用)
  145. */
  146. static void l_state_callback(rtmp_ctx_t *ctx, rtmp_state_t oldstate, rtmp_state_t newstate, int error_code) {
  147. rtos_msg_t msg = {0};
  148. msg.handler = l_rtmp_handler;
  149. msg.ptr = ctx->user_data;
  150. msg.arg1 = (int)newstate;
  151. msg.arg2 = (int)oldstate;
  152. LLOGD("RTMP状态(%d)回调消息入队 %p %p", (int)newstate, &msg, ctx->user_data);
  153. // luat_msgbus_put(&msg, 0);
  154. }
  155. /**
  156. 连接到RTMP服务器
  157. @api rtmp:connect()
  158. @return boolean 成功返回true, 失败返回false
  159. @usage
  160. local ok = rtmp:connect()
  161. if ok then
  162. print("连接请求已发送")
  163. else
  164. print("连接失败")
  165. end
  166. */
  167. static int l_rtmp_connect(lua_State *L) {
  168. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  169. if (!ud || !ud->rtmp) {
  170. lua_pushboolean(L, 0);
  171. return 1;
  172. }
  173. rtmp_set_state_callback(ud->rtmp, l_state_callback);
  174. int ret = tcpip_callback_with_block(rtmp_connect, (void *)ud->rtmp, 0);
  175. LLOGD("RTMP连接请求: %s", ret == 0 ? "成功" : "失败");
  176. lua_pushboolean(L, ret == 0 ? 1 : 0);
  177. return 1;
  178. }
  179. /**
  180. 断开RTMP连接
  181. @api rtmp:disconnect()
  182. @return boolean 成功返回true, 失败返回false
  183. @usage
  184. rtmp:disconnect()
  185. */
  186. static int l_rtmp_disconnect(lua_State *L) {
  187. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  188. if (!ud || !ud->rtmp) {
  189. lua_pushboolean(L, 0);
  190. return 1;
  191. }
  192. int ret = rtmp_disconnect(ud->rtmp);
  193. LLOGD("RTMP断开连接: %s", ret == 0 ? "成功" : "失败");
  194. lua_pushboolean(L, ret == 0 ? 1 : 0);
  195. return 1;
  196. }
  197. static void t_rtmp_poll(void *arg) {
  198. rtmp_ctx_t *ctx = (rtmp_ctx_t *)arg;
  199. rtmp_poll(ctx);
  200. sys_timeout(20, t_rtmp_poll, ctx);
  201. }
  202. /**
  203. 处理RTMP事件
  204. @api rtmp:start()
  205. @return nil 无返回值
  206. @usage
  207. rtmp:start()
  208. */
  209. static int l_rtmp_start(lua_State *L) {
  210. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  211. if (!ud || !ud->rtmp) {
  212. return 0;
  213. }
  214. sys_timeout(20, t_rtmp_poll, ud->rtmp);
  215. return 0;
  216. }
  217. /**
  218. 获取RTMP连接状态
  219. @api rtmp:getState()
  220. @return int 当前状态值
  221. @usage
  222. local state = rtmp:getState()
  223. if state == rtmp.STATE_CONNECTED then
  224. print("已连接")
  225. elseif state == rtmp.STATE_PUBLISHING then
  226. print("正在推流")
  227. end
  228. */
  229. static int l_rtmp_get_state(lua_State *L) {
  230. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  231. if (!ud || !ud->rtmp) {
  232. lua_pushinteger(L, -1);
  233. return 1;
  234. }
  235. rtmp_state_t state = rtmp_get_state(ud->rtmp);
  236. lua_pushinteger(L, (lua_Integer)state);
  237. return 1;
  238. }
  239. /**
  240. 获取RTMP统计信息
  241. @api rtmp:getStats()
  242. @return table 统计信息表
  243. @usage
  244. local stats = rtmp:getStats()
  245. print("已发送字节数:", stats.bytes_sent)
  246. print("已发送视频帧数:", stats.video_frames_sent)
  247. print("已发送音频帧数:", stats.audio_frames_sent)
  248. */
  249. static int l_rtmp_get_stats(lua_State *L) {
  250. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  251. if (!ud || !ud->rtmp) {
  252. lua_pushnil(L);
  253. return 1;
  254. }
  255. rtmp_stats_t stats = {0};
  256. rtmp_get_stats(ud->rtmp, &stats);
  257. lua_newtable(L);
  258. lua_pushinteger(L, stats.bytes_sent);
  259. lua_setfield(L, -2, "bytes_sent");
  260. lua_pushinteger(L, stats.video_frames_sent);
  261. lua_setfield(L, -2, "video_frames_sent");
  262. lua_pushinteger(L, stats.audio_frames_sent);
  263. lua_setfield(L, -2, "audio_frames_sent");
  264. lua_pushinteger(L, stats.connection_time);
  265. lua_setfield(L, -2, "connection_time");
  266. return 1;
  267. }
  268. /**
  269. 销毁RTMP上下文,释放所有资源
  270. @api rtmp:destroy()
  271. @return nil 无返回值
  272. @usage
  273. rtmp:destroy()
  274. */
  275. static int l_rtmp_destroy(lua_State *L) {
  276. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  277. if (!ud || !ud->rtmp) {
  278. return 0;
  279. }
  280. if (ud->callback_ref != LUA_NOREF) {
  281. luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
  282. ud->callback_ref = LUA_NOREF;
  283. }
  284. rtmp_destroy(ud->rtmp);
  285. ud->rtmp = NULL;
  286. LLOGD("RTMP上下文已销毁");
  287. return 0;
  288. }
  289. static int l_rtmp_gc(lua_State *L) {
  290. luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
  291. if (ud && ud->rtmp) {
  292. if (ud->callback_ref != LUA_NOREF) {
  293. luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
  294. }
  295. rtmp_destroy(ud->rtmp);
  296. ud->rtmp = NULL;
  297. }
  298. return 0;
  299. }
  300. #include "rotable2.h"
  301. static const rotable_Reg_t reg_rtmp_ctx[] = {
  302. {"setCallback", ROREG_FUNC(l_rtmp_set_callback)},
  303. {"connect", ROREG_FUNC(l_rtmp_connect)},
  304. {"disconnect", ROREG_FUNC(l_rtmp_disconnect)},
  305. {"start", ROREG_FUNC(l_rtmp_start)},
  306. {"getState", ROREG_FUNC(l_rtmp_get_state)},
  307. {"getStats", ROREG_FUNC(l_rtmp_get_stats)},
  308. {"destroy", ROREG_FUNC(l_rtmp_destroy)},
  309. {"__gc", ROREG_FUNC(l_rtmp_gc)},
  310. {NULL, ROREG_INT(0)}
  311. };
  312. static const rotable_Reg_t reg_rtmp[] = {
  313. {"create", ROREG_FUNC(l_rtmp_create)},
  314. // RTMP状态常量
  315. {"STATE_IDLE", ROREG_INT(RTMP_STATE_IDLE)},
  316. {"STATE_CONNECTING", ROREG_INT(RTMP_STATE_CONNECTING)},
  317. {"STATE_HANDSHAKING", ROREG_INT(RTMP_STATE_HANDSHAKING)},
  318. {"STATE_CONNECTED", ROREG_INT(RTMP_STATE_CONNECTED)},
  319. {"STATE_PUBLISHING", ROREG_INT(RTMP_STATE_PUBLISHING)},
  320. {"STATE_DISCONNECTING",ROREG_INT(RTMP_STATE_DISCONNECTING)},
  321. {"STATE_ERROR", ROREG_INT(RTMP_STATE_ERROR)},
  322. {NULL, ROREG_INT(0)}
  323. };
  324. static int _rtmp_struct_newindex(lua_State *L) {
  325. const rotable_Reg_t* reg = reg_rtmp_ctx;
  326. const char* key = luaL_checkstring(L, 2);
  327. while (1) {
  328. if (reg->name == NULL)
  329. return 0;
  330. if (!strcmp(reg->name, key)) {
  331. lua_pushcfunction(L, reg->value.value.func);
  332. return 1;
  333. }
  334. reg ++;
  335. }
  336. }
  337. LUAMOD_API int luaopen_rtmp(lua_State *L) {
  338. luat_newlib2(L, reg_rtmp);
  339. luaL_newmetatable(L, "rtmp_ctx");
  340. lua_pushcfunction(L, _rtmp_struct_newindex);
  341. lua_setfield(L, -2, "__index");
  342. lua_pop(L, 1);
  343. return 1;
  344. }