Enigma2 plugin to to play various online streams (mostly Latvian).

servicemp3record.cpp 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. #include <lib/service/servicemp3record.h>
  2. #include <lib/base/eerror.h>
  3. #include <lib/dvb/epgcache.h>
  4. #include <lib/dvb/metaparser.h>
  5. #include <lib/base/httpstream.h>
  6. #include <lib/base/nconfig.h>
  7. #include <lib/nav/core.h>
  8. #include <gst/gst.h>
  9. #include <gst/pbutils/missing-plugins.h>
  10. #define HTTP_TIMEOUT 60
  11. DEFINE_REF(eServiceMP3Record);
  12. eServiceMP3Record::eServiceMP3Record(const eServiceReference &ref):
  13. m_ref(ref),
  14. m_streamingsrc_timeout(eTimer::create(eApp)),
  15. m_pump(eApp, 1)
  16. {
  17. m_state = stateIdle;
  18. m_error = 0;
  19. m_simulate = false;
  20. m_recording_pipeline = 0;
  21. m_useragent = "Enigma2 Mediaplayer";
  22. m_extra_headers = "";
  23. CONNECT(m_pump.recv_msg, eServiceMP3Record::gstPoll);
  24. CONNECT(m_streamingsrc_timeout->timeout, eServiceMP3Record::sourceTimeout);
  25. }
  26. eServiceMP3Record::~eServiceMP3Record()
  27. {
  28. if (m_recording_pipeline)
  29. {
  30. // disconnect sync handler callback
  31. GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_recording_pipeline));
  32. #if GST_VERSION_MAJOR < 1
  33. gst_bus_set_sync_handler(bus, NULL, NULL);
  34. #else
  35. gst_bus_set_sync_handler(bus, NULL, NULL, NULL);
  36. #endif
  37. gst_object_unref(bus);
  38. }
  39. if (m_state > stateIdle)
  40. stop();
  41. if (m_recording_pipeline)
  42. {
  43. gst_object_unref(GST_OBJECT(m_recording_pipeline));
  44. }
  45. }
  46. RESULT eServiceMP3Record::prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags, bool descramble, bool recordecm)
  47. {
  48. eDebug("[eMP3ServiceRecord] prepare filename %s", filename);
  49. m_filename = filename;
  50. if (m_state == stateIdle)
  51. {
  52. int ret = doPrepare();
  53. if (!ret)
  54. {
  55. eDVBMetaParser meta;
  56. std::string service_data;
  57. meta.m_time_create = begTime;
  58. meta.m_ref = eServiceReferenceDVB(m_ref.toString());
  59. meta.m_data_ok = 1;
  60. meta.m_service_data = service_data;
  61. if (name)
  62. meta.m_name = name;
  63. if (descr)
  64. meta.m_description = descr;
  65. if (tags)
  66. meta.m_tags = tags;
  67. meta.m_scrambled = recordecm; /* assume we will record scrambled data, when ecm will be included in the recording */
  68. ret = meta.updateMeta(m_filename.c_str()) ? -255 : 0;
  69. if (!ret)
  70. {
  71. std::string fname = m_filename;
  72. fname += "eit";
  73. eEPGCache::getInstance()->saveEventToFile(fname.c_str(), m_ref, eit_event_id, begTime, endTime);
  74. }
  75. m_state = statePrepared;
  76. }
  77. return ret;
  78. }
  79. return -1;
  80. }
  81. RESULT eServiceMP3Record::prepareStreaming(bool descramble, bool includeecm)
  82. {
  83. return -1;
  84. }
  85. RESULT eServiceMP3Record::start(bool simulate)
  86. {
  87. m_simulate = simulate;
  88. m_event((iRecordableService*)this, evStart);
  89. if (simulate)
  90. return 0;
  91. return doRecord();
  92. }
  93. RESULT eServiceMP3Record::stop()
  94. {
  95. if (!m_simulate)
  96. eDebug("[eMP3ServiceRecord] stop recording");
  97. if (m_state == stateRecording)
  98. {
  99. gst_element_set_state(m_recording_pipeline, GST_STATE_NULL);
  100. m_state = statePrepared;
  101. } else if (!m_simulate)
  102. eDebug("[eMP3ServiceRecord] stop was not recording");
  103. if (m_state == statePrepared)
  104. {
  105. if (m_streamingsrc_timeout)
  106. m_streamingsrc_timeout->stop();
  107. m_state = stateIdle;
  108. }
  109. m_event((iRecordableService*)this, evRecordStopped);
  110. return 0;
  111. }
  112. int eServiceMP3Record::doPrepare()
  113. {
  114. if (m_state == stateIdle)
  115. {
  116. gchar *uri;
  117. size_t pos = m_ref.path.find('#');
  118. std::string stream_uri;
  119. if (pos != std::string::npos && (m_ref.path.compare(0, 4, "http") == 0 || m_ref.path.compare(0, 4, "rtsp") == 0))
  120. {
  121. stream_uri = m_ref.path.substr(0, pos);
  122. m_extra_headers = m_ref.path.substr(pos + 1);
  123. pos = m_extra_headers.find("User-Agent=");
  124. if (pos != std::string::npos)
  125. {
  126. size_t hpos_start = pos + 11;
  127. size_t hpos_end = m_extra_headers.find('&', hpos_start);
  128. if (hpos_end != std::string::npos)
  129. m_useragent = m_extra_headers.substr(hpos_start, hpos_end - hpos_start);
  130. else
  131. m_useragent = m_extra_headers.substr(hpos_start);
  132. }
  133. }
  134. else
  135. {
  136. stream_uri = m_ref.path;
  137. }
  138. eDebug("[eMP3ServiceRecord] doPrepare uri=%s", stream_uri.c_str());
  139. uri = g_strdup_printf ("%s", stream_uri.c_str());
  140. m_recording_pipeline = gst_pipeline_new ("recording-pipeline");
  141. m_source = gst_element_factory_make("uridecodebin", "uridec");
  142. GstElement* sink = gst_element_factory_make("filesink", "fsink");
  143. // set uridecodebin properties and notify
  144. g_object_set(m_source, "uri", uri, NULL);
  145. g_object_set(m_source, "caps", gst_caps_from_string("video/mpegts;video/x-flv;video/x-matroska;video/quicktime;video/x-msvideo;video/x-ms-asf;audio/mpeg;audio/x-flac;audio/x-ac3"), NULL);
  146. g_signal_connect(m_source, "notify::source", G_CALLBACK(handleUridecNotifySource), this);
  147. g_signal_connect(m_source, "pad-added", G_CALLBACK(handlePadAdded), sink);
  148. g_signal_connect(m_source, "autoplug-continue", G_CALLBACK(handleAutoPlugCont), this);
  149. // set sink properties
  150. g_object_set(sink, "location", m_filename.c_str(), NULL);
  151. g_free(uri);
  152. if (m_recording_pipeline && m_source && sink)
  153. {
  154. gst_bin_add_many(GST_BIN(m_recording_pipeline), m_source, sink, NULL);
  155. GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_recording_pipeline));
  156. #if GST_VERSION_MAJOR < 1
  157. gst_bus_set_sync_handler(bus, gstBusSyncHandler, this);
  158. #else
  159. gst_bus_set_sync_handler(bus, gstBusSyncHandler, this, NULL);
  160. #endif
  161. gst_object_unref(bus);
  162. }
  163. else
  164. {
  165. m_recording_pipeline = 0;
  166. eDebug("[eServiceMP3Record] doPrepare Sorry, cannot record: Failed to create GStreamer pipeline!");
  167. return -1;
  168. }
  169. }
  170. return 0;
  171. }
  172. int eServiceMP3Record::doRecord()
  173. {
  174. int err = doPrepare();
  175. if (err)
  176. {
  177. m_error = errMisconfiguration;
  178. m_event((iRecordableService*)this, evRecordFailed);
  179. return err;
  180. }
  181. if (gst_element_set_state(m_recording_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
  182. {
  183. eDebug("[eMP3ServiceRecord] doRecord error cannot set pipeline to state_playing");
  184. m_error = errMisconfiguration;
  185. m_event((iRecordableService*)this, evRecordFailed);
  186. return -1;
  187. }
  188. m_state = stateRecording;
  189. m_error = 0;
  190. m_event((iRecordableService*)this, evRecordRunning);
  191. return 0;
  192. }
  193. void eServiceMP3Record::gstPoll(ePtr<GstMessageContainer> const &msg)
  194. {
  195. switch (msg->getType())
  196. {
  197. case 1:
  198. {
  199. GstMessage *gstmessage = *((GstMessageContainer*)msg);
  200. if (gstmessage)
  201. {
  202. gstBusCall(gstmessage);
  203. }
  204. break;
  205. }
  206. default:
  207. eDebug("[eMP3ServiceRecord] gstPoll error unknown message type");
  208. }
  209. }
  210. void eServiceMP3Record::sourceTimeout()
  211. {
  212. eDebug("[eMP3ServiceRecord] sourceTimeout recording failed");
  213. m_event((iRecordableService*)this, evRecordFailed);
  214. }
  215. void eServiceMP3Record::gstBusCall(GstMessage *msg)
  216. {
  217. if (!msg)
  218. return;
  219. ePtr<iRecordableService> ptr = this;
  220. gchar *sourceName;
  221. GstObject *source;
  222. source = GST_MESSAGE_SRC(msg);
  223. if (!GST_IS_OBJECT(source))
  224. return;
  225. sourceName = gst_object_get_name(source);
  226. switch (GST_MESSAGE_TYPE (msg))
  227. {
  228. case GST_MESSAGE_EOS:
  229. eDebug("[eMP3ServiceRecord] gstBusCall eos event");
  230. // Stream end -> stop recording
  231. m_event((iRecordableService*)this, evGstRecordEnded);
  232. break;
  233. case GST_MESSAGE_STATE_CHANGED:
  234. {
  235. if(GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
  236. break;
  237. GstState old_state, new_state;
  238. gst_message_parse_state_changed(msg, &old_state, &new_state, NULL);
  239. if(old_state == new_state)
  240. break;
  241. GstStateChange transition = (GstStateChange)GST_STATE_TRANSITION(old_state, new_state);
  242. eDebug("[eMP3ServiceRecord] gstBusCall state transition %s -> %s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));
  243. switch(transition)
  244. {
  245. case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
  246. {
  247. if (m_streamingsrc_timeout)
  248. m_streamingsrc_timeout->stop();
  249. break;
  250. }
  251. default:
  252. break;
  253. }
  254. break;
  255. }
  256. case GST_MESSAGE_ERROR:
  257. {
  258. gchar *debug;
  259. GError *err;
  260. gst_message_parse_error(msg, &err, &debug);
  261. g_free(debug);
  262. if (err->code != GST_STREAM_ERROR_CODEC_NOT_FOUND)
  263. eWarning("[eServiceMP3Record] gstBusCall Gstreamer error: %s (%i) from %s", err->message, err->code, sourceName);
  264. g_error_free(err);
  265. break;
  266. }
  267. case GST_MESSAGE_ELEMENT:
  268. {
  269. const GstStructure *msgstruct = gst_message_get_structure(msg);
  270. if (msgstruct)
  271. {
  272. if (gst_is_missing_plugin_message(msg))
  273. {
  274. GstCaps *caps = NULL;
  275. gst_structure_get (msgstruct, "detail", GST_TYPE_CAPS, &caps, NULL);
  276. if (caps)
  277. {
  278. std::string codec = (const char*) gst_caps_to_string(caps);
  279. eDebug("[eServiceMP3Record] gstBusCall cannot record because of incompatible codecs %s", codec.c_str());
  280. gst_caps_unref(caps);
  281. }
  282. }
  283. else
  284. {
  285. const gchar *eventname = gst_structure_get_name(msgstruct);
  286. if (eventname)
  287. {
  288. if (!strcmp(eventname, "redirect"))
  289. {
  290. const char *uri = gst_structure_get_string(msgstruct, "new-location");
  291. eDebug("[eServiceMP3Record] gstBusCall redirect to %s", uri);
  292. gst_element_set_state (m_recording_pipeline, GST_STATE_NULL);
  293. g_object_set(G_OBJECT (m_source), "uri", uri, NULL);
  294. gst_element_set_state (m_recording_pipeline, GST_STATE_PLAYING);
  295. }
  296. }
  297. }
  298. }
  299. break;
  300. }
  301. case GST_MESSAGE_STREAM_STATUS:
  302. {
  303. GstStreamStatusType type;
  304. GstElement *owner;
  305. gst_message_parse_stream_status (msg, &type, &owner);
  306. if (type == GST_STREAM_STATUS_TYPE_CREATE)
  307. {
  308. if (GST_IS_PAD(source))
  309. owner = gst_pad_get_parent_element(GST_PAD(source));
  310. else if (GST_IS_ELEMENT(source))
  311. owner = GST_ELEMENT(source);
  312. else
  313. owner = 0;
  314. if (owner)
  315. {
  316. GstState state;
  317. gst_element_get_state(m_recording_pipeline, &state, NULL, 0LL);
  318. GstElementFactory *factory = gst_element_get_factory(GST_ELEMENT(owner));
  319. const gchar *name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
  320. if (!strcmp(name, "souphttpsrc") && (state == GST_STATE_READY) && !m_streamingsrc_timeout->isActive())
  321. {
  322. m_streamingsrc_timeout->start(HTTP_TIMEOUT*1000, true);
  323. g_object_set (G_OBJECT (owner), "timeout", HTTP_TIMEOUT, NULL);
  324. eDebug("[eServiceMP3Record] gstBusCall setting timeout on %s to %is", name, HTTP_TIMEOUT);
  325. }
  326. }
  327. if (GST_IS_PAD(source))
  328. gst_object_unref(owner);
  329. }
  330. break;
  331. }
  332. default:
  333. break;
  334. }
  335. g_free(sourceName);
  336. }
  337. void eServiceMP3Record::handleMessage(GstMessage *msg)
  338. {
  339. if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_STATE_CHANGED && GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
  340. {
  341. /*
  342. * ignore verbose state change messages for all active elements;
  343. * we only need to handle state-change events for the recording pipeline
  344. */
  345. gst_message_unref(msg);
  346. return;
  347. }
  348. m_pump.send(new GstMessageContainer(1, msg, NULL, NULL));
  349. }
  350. GstBusSyncReply eServiceMP3Record::gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data)
  351. {
  352. eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
  353. if (_this) _this->handleMessage(message);
  354. return GST_BUS_DROP;
  355. }
  356. void eServiceMP3Record::handleUridecNotifySource(GObject *object, GParamSpec *unused, gpointer user_data)
  357. {
  358. GstElement *source = NULL;
  359. eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
  360. g_object_get(object, "source", &source, NULL);
  361. if (source)
  362. {
  363. if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict") != 0)
  364. {
  365. g_object_set(G_OBJECT(source), "ssl-strict", FALSE, NULL);
  366. }
  367. if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent") != 0 && !_this->m_useragent.empty())
  368. {
  369. g_object_set(G_OBJECT(source), "user-agent", _this->m_useragent.c_str(), NULL);
  370. }
  371. if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0 && !_this->m_extra_headers.empty())
  372. {
  373. #if GST_VERSION_MAJOR < 1
  374. GstStructure *extras = gst_structure_empty_new("extras");
  375. #else
  376. GstStructure *extras = gst_structure_new_empty("extras");
  377. #endif
  378. size_t pos = 0;
  379. while (pos != std::string::npos)
  380. {
  381. std::string name, value;
  382. size_t start = pos;
  383. size_t len = std::string::npos;
  384. pos = _this->m_extra_headers.find('=', pos);
  385. if (pos != std::string::npos)
  386. {
  387. len = pos - start;
  388. pos++;
  389. name = _this->m_extra_headers.substr(start, len);
  390. start = pos;
  391. len = std::string::npos;
  392. pos = _this->m_extra_headers.find('&', pos);
  393. if (pos != std::string::npos)
  394. {
  395. len = pos - start;
  396. pos++;
  397. }
  398. value = _this->m_extra_headers.substr(start, len);
  399. }
  400. if (!name.empty() && !value.empty())
  401. {
  402. GValue header;
  403. eDebug("[eServiceMP3Record] handleUridecNotifySource setting extra-header '%s:%s'", name.c_str(), value.c_str());
  404. memset(&header, 0, sizeof(GValue));
  405. g_value_init(&header, G_TYPE_STRING);
  406. g_value_set_string(&header, value.c_str());
  407. gst_structure_set_value(extras, name.c_str(), &header);
  408. }
  409. else
  410. {
  411. eDebug("[eServiceMP3Record] handleUridecNotifySource invalid header format %s", _this->m_extra_headers.c_str());
  412. break;
  413. }
  414. }
  415. if (gst_structure_n_fields(extras) > 0)
  416. {
  417. g_object_set(G_OBJECT(source), "extra-headers", extras, NULL);
  418. }
  419. gst_structure_free(extras);
  420. }
  421. gst_object_unref(source);
  422. }
  423. }
  424. void eServiceMP3Record::handlePadAdded(GstElement *element, GstPad *pad, gpointer user_data)
  425. {
  426. GstElement *sink= (GstElement*)user_data;
  427. GstPad *filesink_pad = gst_element_get_static_pad(sink, "sink");
  428. if (gst_pad_is_linked(filesink_pad))
  429. {
  430. gst_object_unref(filesink_pad);
  431. return;
  432. }
  433. if (gst_pad_link(pad, filesink_pad) != GST_PAD_LINK_OK)
  434. {
  435. eDebug("[eServiceMP3Record] handlePadAdded cannot link uridecodebin with filesink");
  436. }
  437. else
  438. {
  439. eDebug("[eServiceMP3Record] handlePadAdded pads linked -> recording starts");
  440. }
  441. gst_object_unref(filesink_pad);
  442. }
  443. gboolean eServiceMP3Record::handleAutoPlugCont(GstElement *bin, GstPad *pad, GstCaps *caps, gpointer user_data)
  444. {
  445. eDebug("[eMP3ServiceRecord] handleAutoPlugCont found caps %s", gst_caps_to_string(caps));
  446. return true;
  447. }
  448. RESULT eServiceMP3Record::frontendInfo(ePtr<iFrontendInformation> &ptr)
  449. {
  450. ptr = 0;
  451. return -1;
  452. }
  453. RESULT eServiceMP3Record::connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection)
  454. {
  455. connection = new eConnection((iRecordableService*)this, m_event.connect(event));
  456. return 0;
  457. }
  458. RESULT eServiceMP3Record::stream(ePtr<iStreamableService> &ptr)
  459. {
  460. ptr = 0;
  461. return -1;
  462. }
  463. RESULT eServiceMP3Record::subServices(ePtr<iSubserviceList> &ptr)
  464. {
  465. ptr = 0;
  466. return -1;
  467. }