implement state change handler and move encoder start into PLAYING
[gst-plugin-dreamsource.git] / src / gstdreamvideosource.c
1 /*
2  * GStreamer dreamvideosource
3  * Copyright 2014-2015 Andreas Frisch <fraxinas@opendreambox.org>
4  *
5  * This program is licensed under the Creative Commons
6  * Attribution-NonCommercial-ShareAlike 3.0 Unported
7  * License. To view a copy of this license, visit
8  * http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
9  * Creative Commons,559 Nathan Abbott Way,Stanford,California 94305,USA.
10  *
11  * Alternatively, this program may be distributed and executed on
12  * hardware which is licensed by Dream Multimedia GmbH.
13  *
14  * This program is NOT free software. It is open source, you are allowed
15  * to modify it (if you keep the license), but it may not be commercially
16  * distributed other than under the conditions noted above.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <gst/gst.h>
24 #include "gstdreamvideosource.h"
25
26 GST_DEBUG_CATEGORY_STATIC (dreamvideosource_debug);
27 #define GST_CAT_DEFAULT dreamvideosource_debug
28
29 enum
30 {
31         SIGNAL_GET_BASE_PTS,
32         LAST_SIGNAL
33 };
34
35 enum
36 {
37   ARG_0,
38   ARG_CAPS,
39   ARG_BITRATE,
40 };
41
42 static guint gst_dreamvideosource_signals[LAST_SIGNAL] = { 0 };
43
44
45 #define DEFAULT_BITRATE 2048
46 #define DEFAULT_FRAMERATE 25
47
48 static GstStaticPadTemplate srctemplate =
49     GST_STATIC_PAD_TEMPLATE ("src",
50         GST_PAD_SRC,
51         GST_PAD_ALWAYS,
52         GST_STATIC_CAPS ("video/x-h264, "
53         "width = { 720, 1280, 1920 }, "
54         "height = { 576, 720, 1080 }, "
55         "framerate = { 1/25, 1/30, 1/50, 1/60 }, "
56         "pixel-aspect-ratio = { 5/4, 16/9 }, "
57         "stream-format = (string) byte-stream, "
58         "profile = (string) main")
59     );
60
61 #define gst_dreamvideosource_parent_class parent_class
62 G_DEFINE_TYPE (GstDreamVideoSource, gst_dreamvideosource, GST_TYPE_PUSH_SRC);
63
64 static GstCaps *gst_dreamvideosource_getcaps (GstBaseSrc * psrc, GstCaps * filter);
65 static gboolean gst_dreamvideosource_setcaps (GstBaseSrc * bsrc, GstCaps * caps);
66 static GstCaps *gst_dreamvideosource_fixate (GstBaseSrc * bsrc, GstCaps * caps);
67 static gboolean gst_dreamvideosource_start (GstBaseSrc * bsrc);
68 static gboolean gst_dreamvideosource_stop (GstBaseSrc * bsrc);
69 static void gst_dreamvideosource_finalize (GObject * gobject);
70 static GstFlowReturn gst_dreamvideosource_create (GstPushSrc * psrc, GstBuffer ** outbuf);
71
72 static void gst_dreamvideosource_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
73 static void gst_dreamvideosource_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
74
75 static GstStateChangeReturn gst_dreamvideosource_change_state (GstElement * element, GstStateChange transition);
76 static gint64 gst_dreamvideosource_get_base_pts (GstDreamVideoSource *self);
77
78 static void
79 gst_dreamvideosource_class_init (GstDreamVideoSourceClass * klass)
80 {
81         GObjectClass *gobject_class;
82         GstElementClass *gstelement_class;
83         GstBaseSrcClass *gstbasesrc_class;
84         GstPushSrcClass *gstpush_src_class;
85
86         gobject_class = (GObjectClass *) klass;
87         gstelement_class = (GstElementClass *) klass;
88         gstbasesrc_class = (GstBaseSrcClass *) klass;
89         gstpush_src_class = (GstPushSrcClass *) klass;
90
91         gobject_class->set_property = gst_dreamvideosource_set_property;
92         gobject_class->get_property = gst_dreamvideosource_get_property;
93         gobject_class->finalize = gst_dreamvideosource_finalize;
94
95         gst_element_class_add_pad_template (gstelement_class,
96                                             gst_static_pad_template_get (&srctemplate));
97
98         gst_element_class_set_static_metadata (gstelement_class,
99             "Dream Video source", "Source/Video",
100             "Provide an h.264 video elementary stream from Dreambox encoder device",
101             "Andreas Frisch <fraxinas@opendreambox.org>");
102         
103         gstelement_class->change_state = gst_dreamvideosource_change_state;
104
105         gstbasesrc_class->get_caps = gst_dreamvideosource_getcaps;
106         gstbasesrc_class->start = gst_dreamvideosource_start;
107         gstbasesrc_class->stop = gst_dreamvideosource_stop;
108
109         gstpush_src_class->create = gst_dreamvideosource_create;
110         
111         g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BITRATE,
112           g_param_spec_int ("bitrate", "Bitrate (kb/s)",
113             "Bitrate in kbit/sec", 16, 200000, DEFAULT_BITRATE,
114             G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
115
116         gst_dreamvideosource_signals[SIGNAL_GET_BASE_PTS] =
117                 g_signal_new ("get-base-pts",
118                 G_TYPE_FROM_CLASS (klass),
119                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
120                 G_STRUCT_OFFSET (GstDreamVideoSourceClass, get_base_pts),
121                 NULL, NULL, gst_dreamsource_marshal_INT64__VOID, G_TYPE_INT64, 0);
122
123         klass->get_base_pts = gst_dreamvideosource_get_base_pts;
124 }
125
126 static gint64
127 gst_dreamvideosource_get_base_pts (GstDreamVideoSource *self)
128 {
129         GST_DEBUG_OBJECT (self, "gst_dreamvideosource_get_base_pts " GST_TIME_FORMAT"", GST_TIME_ARGS (self->base_pts) );
130         return self->base_pts;
131 }
132
133 gboolean
134 gst_dreamvideosource_plugin_init (GstPlugin *plugin)
135 {
136         GST_DEBUG_CATEGORY_INIT (dreamvideosource_debug, "dreamvideosource", 0, "dreamvideosource");
137         return gst_element_register (plugin, "dreamvideosource", GST_RANK_PRIMARY, GST_TYPE_DREAMVIDEOSOURCE);
138 }
139
140 static void
141 gst_dreamvideosource_init (GstDreamVideoSource * self)
142 {
143         self->encoder = NULL;
144         self->descriptors_available = 0;
145         self->video_info.width = 1280;
146         self->video_info.height = 720;
147         self->video_info.par_n = 16;
148         self->video_info.par_d = 16;
149         self->video_info.fps_n = 25;
150         self->video_info.fps_d = 1;
151         self->base_pts = GST_CLOCK_TIME_NONE;
152
153         g_mutex_init (&self->mutex);
154         
155         gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME);
156         gst_base_src_set_live (GST_BASE_SRC (self), TRUE);
157 }
158
159 static void gst_dreamvideosource_set_bitrate (GstDreamVideoSource * self, uint32_t bitrate)
160 {
161         if (!self->encoder || !self->encoder->fd)
162                 return;
163         g_mutex_lock (&self->mutex);
164         uint32_t vbr = bitrate*1000;            
165         int ret = ioctl(self->encoder->fd, VENC_SET_BITRATE, &vbr);
166         if (ret != 0)
167         {
168                 GST_WARNING_OBJECT (self, "can't set video bitrate to %i bytes/s!", vbr);
169                 g_mutex_unlock (&self->mutex);
170                 return;
171         }
172         GST_INFO_OBJECT (self, "set video bitrate to %i kBytes/s", bitrate);
173         self->video_info.bitrate = vbr;
174         g_mutex_unlock (&self->mutex);
175 }
176
177 static void
178 gst_dreamvideosource_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
179 {
180         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (object);
181         
182         switch (prop_id) {
183                 case ARG_BITRATE:
184                         gst_dreamvideosource_set_bitrate(self, g_value_get_int (value));
185                         break;
186                 default:
187                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
188                         break;
189         }
190 }
191
192 static void
193 gst_dreamvideosource_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
194 {
195         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (object);
196         
197         switch (prop_id) {
198                 case ARG_BITRATE:
199                         g_value_set_int (value, self->video_info.bitrate/1000);
200                         break;
201                 default:
202                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
203                         break;
204         }
205 }
206
207 static GstCaps *
208 gst_dreamvideosource_getcaps (GstBaseSrc * bsrc, GstCaps * filter)
209 {
210         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (bsrc);
211         GstPadTemplate *pad_template;
212         GstCaps *caps;
213
214         pad_template = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS(self), "src");
215         g_return_val_if_fail (pad_template != NULL, NULL);
216
217         if (self->encoder == NULL) {
218                 GST_DEBUG_OBJECT (self, "encoder not opened -> use template caps");
219                 caps = gst_pad_template_get_caps (pad_template);
220         }
221         else
222         {
223                 caps = gst_caps_make_writable(gst_pad_template_get_caps (pad_template));
224                 gst_caps_set_simple(caps, "width", G_TYPE_INT, self->video_info.width, NULL);
225                 gst_caps_set_simple(caps, "height", G_TYPE_INT, self->video_info.height, NULL);
226                 gst_caps_set_simple(caps, "framerate", GST_TYPE_FRACTION, self->video_info.fps_n, self->video_info.fps_d, NULL);
227         }
228
229         GST_INFO_OBJECT (self, "return caps %" GST_PTR_FORMAT, caps);
230         return caps;
231 }
232
233 static void
234 gst_dreamvideosource_free_buffer (GstDreamVideoSource * self)
235 {
236         GST_LOG_OBJECT (self, "gst_dreamvideosource_free_buffer");
237 }
238
239 static GstFlowReturn
240 gst_dreamvideosource_create (GstPushSrc * psrc, GstBuffer ** outbuf)
241 {
242         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (psrc);
243         EncoderInfo *enc = self->encoder;
244         
245         GST_LOG_OBJECT (self, "new buffer requested");
246
247         if (!enc) {
248                 GST_WARNING_OBJECT (self, "encoder device not opened!");
249                 return GST_FLOW_ERROR;
250         }
251         
252         while (1)
253         {
254                 *outbuf = NULL;
255                 
256                 if (self->descriptors_available == 0)
257                 {
258                         self->descriptors_count = 0;
259                         int rlen = read(enc->fd, enc->buffer, VBUFSIZE);
260                         if (rlen <= 0 || rlen % VBDSIZE ) {
261                                 GST_WARNING_OBJECT (self, "read error %d (errno %i)", rlen, errno);
262                                 return GST_FLOW_ERROR;
263                         }
264                         self->descriptors_available = rlen / VBDSIZE;
265                         GST_LOG_OBJECT (self, "encoder buffer was empty, %d descriptors available", self->descriptors_available);
266                 }
267
268                 while (self->descriptors_count < self->descriptors_available) {
269                         off_t offset = self->descriptors_count * VBDSIZE;
270                         VideoBufferDescriptor *desc = (VideoBufferDescriptor*)(&enc->buffer[offset]);
271
272                         uint32_t f = desc->stCommon.uiFlags;
273
274                         GST_LOG_OBJECT (self, "descriptors_count=%d, descriptors_available=%d\tuiOffset=%d, uiLength=%d", self->descriptors_count, self->descriptors_available, desc->stCommon.uiOffset, desc->stCommon.uiLength);
275
276                         if (G_UNLIKELY (f & CDB_FLAG_METADATA))
277                         { 
278                                 GST_LOG_OBJECT (self, "CDB_FLAG_METADATA... skip outdated packet");
279                                 self->descriptors_count = self->descriptors_available;
280                                 continue;
281                         }
282                         
283                         if (f & VBD_FLAG_DTS_VALID && desc->uiDTS)
284                         {
285                                 if (G_UNLIKELY (self->base_pts == GST_CLOCK_TIME_NONE))
286                                 {
287                                         if (self->dreamaudiosrc)
288                                         {
289                                                 g_mutex_lock (&self->mutex);
290                                                 guint64 audiosource_base_pts;
291                                                 g_signal_emit_by_name(self->dreamaudiosrc, "get-base-pts", &audiosource_base_pts);
292                                                 if (audiosource_base_pts != GST_CLOCK_TIME_NONE)
293                                                 {
294                                                         GST_DEBUG_OBJECT (self, "use DREAMAUDIOSOURCE's base_pts=%" GST_TIME_FORMAT "", GST_TIME_ARGS (audiosource_base_pts) );
295                                                         self->base_pts = audiosource_base_pts;
296                                                 }
297                                                 g_mutex_unlock (&self->mutex);
298                                         }
299                                         if (self->base_pts == GST_CLOCK_TIME_NONE)
300                                         {
301                                                 self->base_pts = MPEGTIME_TO_GSTTIME(desc->uiDTS);
302                                                 GST_DEBUG_OBJECT (self, "use mpeg stream pts as base_pts=%" GST_TIME_FORMAT"", GST_TIME_ARGS (self->base_pts) );
303                                         }
304                                 }
305                         }
306                         
307                         *outbuf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, enc->cdb, VMMAPSIZE, desc->stCommon.uiOffset, desc->stCommon.uiLength, self, (GDestroyNotify)gst_dreamvideosource_free_buffer);
308                         
309                         if (f & CDB_FLAG_PTS_VALID)
310                         {
311                                 GstClockTime buffer_time = MPEGTIME_TO_GSTTIME(desc->stCommon.uiPTS);
312                                 if (self->base_pts != GST_CLOCK_TIME_NONE && buffer_time > self->base_pts )
313                                 {
314                                         buffer_time -= self->base_pts;
315                                         GST_BUFFER_PTS(*outbuf) = buffer_time;
316                                         GST_BUFFER_DTS(*outbuf) = buffer_time;
317                                 }
318                         }
319 #ifdef dump
320                         int wret = write(self->dumpfd, (unsigned char*)(enc->cdb + desc->stCommon.uiOffset), desc->stCommon.uiLength);
321                         GST_LOG_OBJECT (self, "read %i dumped %i total %" G_GSIZE_FORMAT " ", desc->stCommon.uiLength, wret, gst_buffer_get_size (*outbuf) );
322 #endif
323
324                         self->descriptors_count++;
325
326                         break;
327                 }
328
329                 if (self->descriptors_count == self->descriptors_available) {
330                         GST_LOG_OBJECT (self, "self->descriptors_count == self->descriptors_available -> release %i consumed descriptors", self->descriptors_count);
331                         /* release consumed descs */
332                         if (write(enc->fd, &self->descriptors_count, 4) != 4) {
333                                 GST_WARNING_OBJECT (self, "release consumed descs write error!");
334                                 return GST_FLOW_ERROR;
335                         }
336                         self->descriptors_available = 0;
337                 }
338                 
339                 if (*outbuf)
340                 {
341                         GST_DEBUG_OBJECT (self, "pushing %" GST_PTR_FORMAT "", *outbuf );
342                         return GST_FLOW_OK;
343                 }
344
345         }
346         return GST_FLOW_ERROR;
347 }
348
349 static GstStateChangeReturn gst_dreamvideosource_change_state (GstElement * element, GstStateChange transition)
350 {
351         g_return_val_if_fail (GST_DREAMVIDEOSOURCE (element), FALSE);
352         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (element);
353         int ret;
354
355         switch (transition) {
356                 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
357                         GST_LOG_OBJECT (self, "GST_STATE_CHANGE_PAUSED_TO_PLAYING");
358                         ret = ioctl(self->encoder->fd, VENC_START);
359                         if ( ret != 0 )
360                         {
361                                 GST_ERROR_OBJECT(self,"can't start encoder ioctl!");
362                                 return GST_STATE_CHANGE_FAILURE;
363                         }
364                         GST_INFO_OBJECT (self, "started encoder!");
365                         break;
366                 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
367                         GST_LOG_OBJECT (self, "GST_STATE_CHANGE_PLAYING_TO_PAUSED");
368                         ret = ioctl(self->encoder->fd, VENC_STOP);
369                         if ( ret != 0 )
370                         {
371                                 GST_ERROR_OBJECT(self,"can't stop encoder ioctl!");
372                                 return GST_STATE_CHANGE_FAILURE;
373                         }
374                         break;
375                 default:
376                         break;
377         }
378
379         if (GST_ELEMENT_CLASS (parent_class)->change_state)
380                 return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
381
382         return GST_STATE_CHANGE_SUCCESS;
383 }
384 static gboolean
385 gst_dreamvideosource_start (GstBaseSrc * bsrc)
386 {
387         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (bsrc);
388
389         char fn_buf[32];
390
391         self->encoder = malloc(sizeof(EncoderInfo));
392
393         if(!self->encoder) {
394                 GST_ERROR_OBJECT(self,"out of space");
395                 return FALSE;
396         }
397
398         sprintf(fn_buf, "/dev/venc%d", 0);
399         self->encoder->fd = open(fn_buf, O_RDWR | O_SYNC);
400         if(self->encoder->fd <= 0) {
401                 GST_ERROR_OBJECT(self,"cannot open device %s (%s)", fn_buf, strerror(errno));
402                 free(self->encoder);
403                 self->encoder = NULL;
404                 return FALSE;
405         }
406
407         self->encoder->buffer = malloc(VBUFSIZE);
408         if(!self->encoder->buffer) {
409                 GST_ERROR_OBJECT(self,"cannot alloc buffer");
410                 return FALSE;
411         }
412
413         self->encoder->cdb = (unsigned char *)mmap(0, VMMAPSIZE, PROT_READ, MAP_PRIVATE, self->encoder->fd, 0);
414
415         if(!self->encoder->cdb) {
416                 GST_ERROR_OBJECT(self,"cannot mmap cdb");
417                 return FALSE;
418         }
419 #ifdef dump
420         self->dumpfd = open("/media/hdd/movie/dreamvideosource.dump", O_WRONLY | O_CREAT | O_TRUNC);
421         GST_DEBUG_OBJECT (self, "dumpfd = %i (%s)", self->dumpfd, (self->dumpfd > 0) ? "OK" : strerror(errno));
422 #endif
423
424         gst_dreamvideosource_set_bitrate(self, DEFAULT_BITRATE);
425
426         self->dreamaudiosrc = gst_bin_get_by_name_recurse_up(GST_BIN(GST_ELEMENT_PARENT(self)), "dreamaudiosource0");
427
428         return TRUE;
429 }
430
431 static gboolean
432 gst_dreamvideosource_stop (GstBaseSrc * bsrc)
433 {
434         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (bsrc);
435         if (self->encoder) {
436                 if (self->encoder->fd > 0)
437                         ioctl(self->encoder->fd, VENC_STOP);
438                 close(self->encoder->fd);
439         }
440 #ifdef dump
441         close(self->dumpfd);
442 #endif
443         GST_DEBUG_OBJECT (self, "closed");
444         return TRUE;
445 }
446
447 static void
448 gst_dreamvideosource_finalize (GObject * gobject)
449 {
450         GstDreamVideoSource *self = GST_DREAMVIDEOSOURCE (gobject);
451         if (self->encoder) {
452                 if (self->encoder->buffer)
453                         free(self->encoder->buffer);
454                 if (self->encoder->cdb) 
455                         munmap(self->encoder->cdb, VMMAPSIZE);
456                 free(self->encoder);
457         }
458         g_mutex_clear (&self->mutex);
459         GST_DEBUG_OBJECT (self, "finalized");
460         G_OBJECT_CLASS (parent_class)->finalize (gobject);
461 }