fix seeking, implement time position query
[gst-plugin-dvbmediasink.git] / src / gstdvbaudiosink.c
1 /*
2  * GStreamer DVB Media Sink
3  * Copyright 2006 Felix Domke <tmbinc@elitedvb.net>
4  * based on code by:
5  * Copyright 2005 Thomas Vander Stichele <thomas@apestaart.org>
6  * Copyright 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
7  * 
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  *
26  * Alternatively, the contents of this file may be used under the
27  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
28  * which case the following provisions apply instead of the ones
29  * mentioned above:
30  *
31  * This library is free software; you can redistribute it and/or
32  * modify it under the terms of the GNU Library General Public
33  * License as published by the Free Software Foundation; either
34  * version 2 of the License, or (at your option) any later version.
35  *
36  * This library is distributed in the hope that it will be useful,
37  * but WITHOUT ANY WARRANTY; without even the implied warranty of
38  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
39  * Library General Public License for more details.
40  *
41  * You should have received a copy of the GNU Library General Public
42  * License along with this library; if not, write to the
43  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
44  * Boston, MA 02111-1307, USA.
45  */
46
47 /**
48  * SECTION:element-plugin
49  *
50  * <refsect2>
51  * <title>Example launch line</title>
52  * <para>
53  * <programlisting>
54  * gst-launch -v -m audiotestsrc ! plugin ! fakesink silent=TRUE
55  * </programlisting>
56  * </para>
57  * </refsect2>
58  */
59
60 #ifdef HAVE_CONFIG_H
61 #include <config.h>
62 #endif
63 #include <unistd.h>
64 #include <string.h>
65 #include <sys/ioctl.h>
66 #include <sys/socket.h>
67 #include <linux/dvb/audio.h>
68 #include <fcntl.h>
69
70 #include <gst/gst.h>
71
72 /* We add a control socket as in fdsrc to make it shutdown quickly when it's blocking on the fd.
73  * Select is used to determine when the fd is ready for use. When the element state is changed,
74  * it happens from another thread while fdsink is select'ing on the fd. The state-change thread 
75  * sends a control message, so fdsink wakes up and changes state immediately otherwise
76  * it would stay blocked until it receives some data. */
77
78 /* the select call is also performed on the control sockets, that way
79  * we can send special commands to unblock the select call */
80 #define CONTROL_STOP                                            'S'              /* stop the select call */
81 #define CONTROL_SOCKETS(sink)    sink->control_sock
82 #define WRITE_SOCKET(sink)                      sink->control_sock[1]
83 #define READ_SOCKET(sink)                        sink->control_sock[0]
84
85 #define SEND_COMMAND(sink, command)                                     \
86 G_STMT_START {                                                                                                                  \
87         unsigned char c; c = command;                                            \
88         write (WRITE_SOCKET(sink), &c, 1);                               \
89 } G_STMT_END
90
91 #define READ_COMMAND(sink, command, res)                                \
92 G_STMT_START {                                                                                                                           \
93         res = read(READ_SOCKET(sink), &command, 1);      \
94 } G_STMT_END
95
96 #include "gstdvbaudiosink.h"
97
98 GST_DEBUG_CATEGORY_STATIC (dvbaudiosink_debug);
99 #define GST_CAT_DEFAULT dvbaudiosink_debug
100
101 /* Filter signals and args */
102 enum {
103         /* FILL ME */
104         LAST_SIGNAL
105 };
106
107 enum {
108         ARG_0,
109         ARG_SILENT
110 };
111
112 static GstStaticPadTemplate sink_factory =
113 GST_STATIC_PAD_TEMPLATE (
114         "sink",
115         GST_PAD_SINK,
116         GST_PAD_ALWAYS,
117         GST_STATIC_CAPS ("audio/mpeg, "
118                 "mpegversion = (int) [ 1, 2 ]; "
119                 "audio/x-private1-ac3")
120 );
121
122 #define DEBUG_INIT(bla) \
123         GST_DEBUG_CATEGORY_INIT (dvbaudiosink_debug, "dvbaudiosink", 0, "dvbaudiosink element");
124
125 GST_BOILERPLATE_FULL (GstDVBAudioSink, gst_dvbaudiosink, GstBaseSink,
126         GST_TYPE_BASE_SINK, DEBUG_INIT);
127
128 static void     gst_dvbaudiosink_set_property (GObject *object, guint prop_id,
129                                                                                                                                                                                                         const GValue *value,
130                                                                                                                                                                                                         GParamSpec *pspec);
131 static void     gst_dvbaudiosink_get_property (GObject *object, guint prop_id,
132                                                                                                                                                                                                         GValue *value,
133                                                                                                                                                                                                         GParamSpec *pspec);
134
135 static gboolean gst_dvbaudiosink_start (GstBaseSink * sink);
136 static gboolean gst_dvbaudiosink_stop (GstBaseSink * sink);
137 static gboolean gst_dvbaudiosink_event (GstBaseSink * sink, GstEvent * event);
138 static GstFlowReturn gst_dvbaudiosink_render (GstBaseSink * sink,
139         GstBuffer * buffer);
140 static gboolean gst_dvbaudiosink_query (GstPad * pad, GstQuery * query);
141 static gboolean gst_dvbaudiosink_unlock (GstBaseSink * basesink);
142 static gboolean gst_dvbaudiosink_set_caps (GstBaseSink * sink, GstCaps * caps);
143
144 static void
145 gst_dvbaudiosink_base_init (gpointer klass)
146 {
147         static GstElementDetails element_details = {
148                 "A DVB audio sink",
149                 "Generic/DVBAudioSink",
150                 "Outputs a MPEG2 PES / ES into a DVB audio device for hardware playback",
151                 "Felix Domke <tmbinc@elitedvb.net>"
152         };
153         GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
154
155         gst_element_class_add_pad_template (element_class,
156                 gst_static_pad_template_get (&sink_factory));
157         gst_element_class_set_details (element_class, &element_details);
158 }
159
160 /* initialize the plugin's class */
161 static void
162 gst_dvbaudiosink_class_init (GstDVBAudioSinkClass *klass)
163 {
164         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
165         GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
166         
167         gobject_class->set_property = gst_dvbaudiosink_set_property;
168         gobject_class->get_property = gst_dvbaudiosink_get_property;
169         
170         gobject_class = G_OBJECT_CLASS (klass);
171         g_object_class_install_property (gobject_class, ARG_SILENT,
172                 g_param_spec_boolean ("silent", "Silent", "Produce verbose output ?",
173                                                                                                         FALSE, G_PARAM_READWRITE));
174
175         gstbasesink_class->get_times = NULL;
176         gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_dvbaudiosink_start);
177         gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_dvbaudiosink_stop);
178         gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_dvbaudiosink_render);
179         gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_dvbaudiosink_event);
180         gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_dvbaudiosink_unlock);
181         gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_dvbaudiosink_set_caps);
182 }
183
184 /* initialize the new element
185  * instantiate pads and add them to element
186  * set functions
187  * initialize structure
188  */
189 static void
190 gst_dvbaudiosink_init (GstDVBAudioSink *klass,
191                 GstDVBAudioSinkClass * gclass)
192 {
193         GstPad *pad;
194         
195         pad = GST_BASE_SINK_PAD (klass);
196         
197         gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_dvbaudiosink_query));
198         
199         klass->silent = FALSE;
200         GST_BASE_SINK (klass)->sync = FALSE;
201 }
202
203 static void
204 gst_dvbaudiosink_set_property (GObject *object, guint prop_id,
205                                                                                                                                         const GValue *value, GParamSpec *pspec)
206 {
207         GstDVBAudioSink *filter;
208
209         g_return_if_fail (GST_IS_DVBAUDIOSINK (object));
210         filter = GST_DVBAUDIOSINK (object);
211
212         switch (prop_id)
213         {
214         case ARG_SILENT:
215                 filter->silent = g_value_get_boolean (value);
216                 break;
217         default:
218                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
219                 break;
220         }
221 }
222
223 static void
224 gst_dvbaudiosink_get_property (GObject *object, guint prop_id,
225                                                                                                                                         GValue *value, GParamSpec *pspec)
226 {
227         GstDVBAudioSink *filter;
228
229         g_return_if_fail (GST_IS_DVBAUDIOSINK (object));
230         filter = GST_DVBAUDIOSINK (object);
231
232         switch (prop_id) {
233         case ARG_SILENT:
234                 g_value_set_boolean (value, filter->silent);
235                 break;
236         default:
237                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
238                 break;
239         }
240 }
241
242 static gboolean
243 gst_dvbaudiosink_query (GstPad * pad, GstQuery * query)
244 {
245         GstDVBAudioSink *self;
246 //      GstFormat format;
247         
248         self = GST_DVBAUDIOSINK (GST_PAD_PARENT (pad));
249         switch (GST_QUERY_TYPE (query)) {
250         default:
251                 return gst_pad_query_default (pad, query);
252         }
253 }
254
255 static gboolean gst_dvbaudiosink_unlock (GstBaseSink * basesink)
256 {
257         GstDVBAudioSink *self = GST_DVBAUDIOSINK (basesink);
258
259         SEND_COMMAND (self, CONTROL_STOP);
260
261         return TRUE;
262 }
263
264 static gboolean 
265 gst_dvbaudiosink_set_caps (GstBaseSink * basesink, GstCaps * caps)
266 {
267         GstDVBAudioSink *self = GST_DVBAUDIOSINK (basesink);
268
269         GstStructure *structure;
270         const char *type;
271         int bypass;
272         
273         if (self->fd < 0)
274                 return FALSE;
275         
276         structure = gst_caps_get_structure (caps, 0);   
277         type = gst_structure_get_name (structure);
278
279         self->skip = 0;
280
281         if (!strcmp(type, "audio/mpeg"))
282                 bypass = 1;
283         else if (!strcmp(type, "audio/x-ac3") || !strcmp(type, "audio/ac3"))
284                 bypass = 0;             
285         else if (!strcmp(type, "audio/x-private1-ac3"))
286         {
287                 bypass = 0;
288                 self->skip = 2;
289         } else
290         {
291                 GST_DEBUG_OBJECT(self, "illegal streamtype %s!\n", type);
292                 return FALSE;
293         }
294
295         GST_DEBUG_OBJECT(self, "setting dvb mode %d\n", bypass);
296         ioctl(self->fd, AUDIO_SET_BYPASS_MODE, bypass);
297
298         return TRUE;
299 }
300
301 static gboolean
302 gst_dvbaudiosink_event (GstBaseSink * sink, GstEvent * event)
303 {
304         GstDVBAudioSink *self;
305         GST_DEBUG_OBJECT (self, "EVENT %s", gst_event_type_get_name(GST_EVENT_TYPE (event)));
306
307         self = GST_DVBAUDIOSINK (sink);
308         switch (GST_EVENT_TYPE (event)) {
309         case GST_EVENT_FLUSH_START:
310                 ioctl(self->fd, AUDIO_CLEAR_BUFFER);
311                 break;
312         case GST_EVENT_FLUSH_STOP:
313                 ioctl(self->fd, AUDIO_CLEAR_BUFFER);
314                 while (1)
315                 {
316                         gchar command;
317                         int res;
318
319                         READ_COMMAND (self, command, res);
320                         if (res < 0)
321                                 break;
322                 }
323                 break;
324         default:
325                 return TRUE;
326         }
327 }
328
329 static GstFlowReturn
330 gst_dvbaudiosink_render (GstBaseSink * sink, GstBuffer * buffer)
331 {
332         unsigned char pes_header[19];
333         GstDVBAudioSink *self;
334         unsigned int size;
335         fd_set readfds;
336         fd_set writefds;
337         gint retval;
338         
339         self = GST_DVBAUDIOSINK (sink);
340
341         int skip = self->skip;
342         
343         size = GST_BUFFER_SIZE (buffer) - skip;
344         
345 //      printf("write %d, timestamp: %08llx\n", GST_BUFFER_SIZE (buffer), (long long)GST_BUFFER_TIMESTAMP(buffer));
346
347         FD_ZERO (&readfds);
348         FD_SET (READ_SOCKET (self), &readfds);
349
350         FD_ZERO (&writefds);
351         FD_SET (self->fd, &writefds);
352
353         do {
354                 GST_DEBUG_OBJECT (self, "going into select, have %d bytes to write",
355                                 size);
356                 retval = select (FD_SETSIZE, &readfds, &writefds, NULL, NULL);
357         } while ((retval == -1 && errno == EINTR));
358
359         if (retval == -1)
360                 goto select_error;
361
362         if (FD_ISSET (READ_SOCKET (self), &readfds)) {
363                 /* read all stop commands */
364                 while (TRUE) {
365                         gchar command;
366                         int res;
367
368                         READ_COMMAND (self, command, res);
369                         if (res < 0) {
370                                 GST_LOG_OBJECT (self, "no more commands");
371                                 /* no more commands */
372                                 break;
373                         }
374                 }
375                 goto stopped;
376         }
377
378         if (self->fd < 0)
379                 return GST_FLOW_OK;
380
381         pes_header[0] = 0;
382         pes_header[1] = 0;
383         pes_header[2] = 1;
384         pes_header[3] = 0xC0;
385
386 #if 1
387                 /* do we have a timestamp? */
388         if (GST_BUFFER_TIMESTAMP(buffer) != GST_CLOCK_TIME_NONE)
389         {
390                 unsigned long long pts = GST_BUFFER_TIMESTAMP(buffer) * 9LL / 100000; /* convert ns to 90kHz */
391                 
392                 pes_header[4] = (size + 13) >> 8;
393                 pes_header[5] = (size + 13) & 0xFF;
394                 
395                 pes_header[6] = 0x80;
396                 pes_header[7] = 0xC0;
397                 
398                 pes_header[8] = 10;
399                 
400                 pes_header[9]   = 0x31 | ((pts >> 29) & 0xE);
401                 pes_header[10] = pts >> 22;
402                 pes_header[11] = 0x01 | ((pts >> 14) & 0xFE);
403                 pes_header[12] = pts >> 7;
404                 pes_header[13] = 0x01 | ((pts << 1) & 0xFE);
405                 
406                 int64_t dts = pts; /* what to use as DTS-PTS offset? */
407                 
408                 pes_header[14] = 0x11 | ((dts >> 29) & 0xE);
409                 pes_header[15] = dts >> 22;
410                 pes_header[16] = 0x01 | ((dts >> 14) & 0xFE);
411                 pes_header[17] = dts >> 7;
412                 pes_header[18] = 0x01 | ((dts << 1) & 0xFE);
413                 write(self->fd, pes_header, 19);
414         } else
415         {
416                 pes_header[4] = (size + 3) >> 8;
417                 pes_header[5] = (size + 3) & 0xFF;
418                 pes_header[6] = 0x80;
419                 pes_header[7] = 0x00;
420                 pes_header[8] = 0;
421                 write(self->fd, pes_header, 9);
422         }
423 #endif
424
425         write(self->fd, GST_BUFFER_DATA (buffer) + skip, GST_BUFFER_SIZE (buffer) - skip);
426
427         return GST_FLOW_OK;
428 select_error:
429         {
430                 GST_ELEMENT_ERROR (self, RESOURCE, READ, (NULL),
431                                 ("select on file descriptor: %s.", g_strerror (errno)));
432                 GST_DEBUG_OBJECT (self, "Error during select");
433                 return GST_FLOW_ERROR;
434         }
435 stopped:
436         {
437                 GST_DEBUG_OBJECT (self, "Select stopped");
438                 ioctl(self->fd, AUDIO_CLEAR_BUFFER);
439                 return GST_FLOW_WRONG_STATE;
440         }
441 }
442
443 static gboolean
444 gst_dvbaudiosink_start (GstBaseSink * basesink)
445 {
446         GstDVBAudioSink *self;
447         self = GST_DVBAUDIOSINK (basesink);
448         self->fd = open("/dev/dvb/adapter0/audio0", O_RDWR);
449 //      self->fd = open("/dump.pes", O_RDWR|O_CREAT, 0555);
450         
451         gint control_sock[2];
452
453         if (socketpair (PF_UNIX, SOCK_STREAM, 0, control_sock) < 0)
454                 goto socket_pair;
455
456         READ_SOCKET (self) = control_sock[0];
457         WRITE_SOCKET (self) = control_sock[1];
458
459         fcntl (READ_SOCKET (self), F_SETFL, O_NONBLOCK);
460         fcntl (WRITE_SOCKET (self), F_SETFL, O_NONBLOCK);
461
462         if (self->fd)
463         {
464                 ioctl(self->fd, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_MEMORY);
465                 ioctl(self->fd, AUDIO_PLAY);
466                 ioctl(self->fd, AUDIO_SET_BYPASS_MODE, 0);
467         }
468         return TRUE;
469         /* ERRORS */
470 socket_pair:
471         {
472                 GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ_WRITE, (NULL),
473                                 GST_ERROR_SYSTEM);
474                 return FALSE;
475         }
476 }
477
478 static gboolean
479 gst_dvbaudiosink_stop (GstBaseSink * basesink)
480 {
481         GstDVBAudioSink *self;
482         self = GST_DVBAUDIOSINK (basesink);
483         if (self->fd >= 0)
484         {
485                 ioctl(self->fd, AUDIO_STOP);
486                 ioctl(self->fd, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_DEMUX);
487                 close(self->fd);
488         }
489         return TRUE;
490 }
491
492 /* entry point to initialize the plug-in
493  * initialize the plug-in itself
494  * register the element factories and pad templates
495  * register the features
496  *
497  * exchange the string 'plugin' with your elemnt name
498  */
499 static gboolean
500 plugin_init (GstPlugin *plugin)
501 {
502         return gst_element_register (plugin, "dvbaudiosink",
503                                                  GST_RANK_NONE,
504                                                  GST_TYPE_DVBAUDIOSINK);
505 }
506
507 /* this is the structure that gstreamer looks for to register plugins
508  *
509  * exchange the strings 'plugin' and 'Template plugin' with you plugin name and
510  * description
511  */
512 GST_PLUGIN_DEFINE (
513         GST_VERSION_MAJOR,
514         GST_VERSION_MINOR,
515         "dvb_audio_out",
516         "DVB Audio Output",
517         plugin_init,
518         VERSION,
519         "LGPL",
520         "GStreamer",
521         "http://gstreamer.net/"
522 )