1 /*
2 * Copyright (C) 2013 Sebastian Dröge <sebastian@centricular.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "gstopenexrdec.h"
26
27 #include <gst/base/base.h>
28 #include <string.h>
29
30 #include <ImfRgbaFile.h>
31 #include <ImfIO.h>
32 #include <ImfInt64.h>
33 using namespace Imf;
34 using namespace Imath;
35
36 /* Memory stream reader */
37 class MemIStream:public IStream
38 {
39 public:
MemIStream(const char * file_name,const guint8 * data,gsize size)40 MemIStream (const char *file_name, const guint8 * data,
41 gsize size):IStream (file_name), data (data), offset (0), size (size)
42 {
43 }
44
45 virtual bool read (char c[], int n);
46 virtual Int64 tellg ();
47 virtual void seekg (Int64 pos);
48 virtual void clear ();
49
50 private:
51 const guint8 *data;
52 gsize offset, size;
53 };
54
read(char c[],int n)55 bool MemIStream::read (char c[], int n)
56 {
57 if (offset + n > size)
58 throw
59 Iex::InputExc ("Unexpected end of file");
60
61 memcpy (c, data + offset, n);
62 offset += n;
63
64 return (offset == size);
65 }
66
tellg()67 Int64 MemIStream::tellg ()
68 {
69 return offset;
70 }
71
72 void
seekg(Int64 pos)73 MemIStream::seekg (Int64 pos)
74 {
75 offset = pos;
76 if (offset > size)
77 offset = size;
78 }
79
80 void
clear()81 MemIStream::clear ()
82 {
83
84 }
85
86 GST_DEBUG_CATEGORY_STATIC (gst_openexr_dec_debug);
87 #define GST_CAT_DEFAULT gst_openexr_dec_debug
88
89 static gboolean gst_openexr_dec_start (GstVideoDecoder * decoder);
90 static gboolean gst_openexr_dec_stop (GstVideoDecoder * decoder);
91 static GstFlowReturn gst_openexr_dec_parse (GstVideoDecoder * decoder,
92 GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos);
93 static gboolean gst_openexr_dec_set_format (GstVideoDecoder * decoder,
94 GstVideoCodecState * state);
95 static GstFlowReturn gst_openexr_dec_handle_frame (GstVideoDecoder * decoder,
96 GstVideoCodecFrame * frame);
97 static gboolean gst_openexr_dec_decide_allocation (GstVideoDecoder * decoder,
98 GstQuery * query);
99
100 static GstStaticPadTemplate gst_openexr_dec_sink_template =
101 GST_STATIC_PAD_TEMPLATE ("sink",
102 GST_PAD_SINK,
103 GST_PAD_ALWAYS,
104 GST_STATIC_CAPS ("image/x-exr")
105 );
106
107 static GstStaticPadTemplate gst_openexr_dec_src_template =
108 GST_STATIC_PAD_TEMPLATE ("src",
109 GST_PAD_SRC,
110 GST_PAD_ALWAYS,
111 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("ARGB64"))
112 );
113
114 #define parent_class gst_openexr_dec_parent_class
115 G_DEFINE_TYPE (GstOpenEXRDec, gst_openexr_dec, GST_TYPE_VIDEO_DECODER);
116 GST_ELEMENT_REGISTER_DEFINE (openexrdec, "openexrdec", GST_RANK_PRIMARY,
117 GST_TYPE_OPENEXR_DEC);
118
119 static void
gst_openexr_dec_class_init(GstOpenEXRDecClass * klass)120 gst_openexr_dec_class_init (GstOpenEXRDecClass * klass)
121 {
122 GstElementClass *element_class;
123 GstVideoDecoderClass *video_decoder_class;
124
125 element_class = (GstElementClass *) klass;
126 video_decoder_class = (GstVideoDecoderClass *) klass;
127
128 gst_element_class_add_static_pad_template (element_class, &gst_openexr_dec_src_template);
129 gst_element_class_add_static_pad_template (element_class, &gst_openexr_dec_sink_template);
130
131 gst_element_class_set_static_metadata (element_class,
132 "OpenEXR decoder",
133 "Codec/Decoder/Video",
134 "Decode EXR streams", "Sebastian Dröge <sebastian@centricular.com>");
135
136 video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_openexr_dec_start);
137 video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_openexr_dec_stop);
138 video_decoder_class->parse = GST_DEBUG_FUNCPTR (gst_openexr_dec_parse);
139 video_decoder_class->set_format =
140 GST_DEBUG_FUNCPTR (gst_openexr_dec_set_format);
141 video_decoder_class->handle_frame =
142 GST_DEBUG_FUNCPTR (gst_openexr_dec_handle_frame);
143 video_decoder_class->decide_allocation = gst_openexr_dec_decide_allocation;
144
145 GST_DEBUG_CATEGORY_INIT (gst_openexr_dec_debug, "openexrdec", 0,
146 "OpenEXR Decoder");
147 }
148
149 static void
gst_openexr_dec_init(GstOpenEXRDec * self)150 gst_openexr_dec_init (GstOpenEXRDec * self)
151 {
152 GstVideoDecoder *decoder = (GstVideoDecoder *) self;
153
154 gst_video_decoder_set_packetized (decoder, FALSE);
155 gst_video_decoder_set_use_default_pad_acceptcaps (GST_VIDEO_DECODER_CAST
156 (self), TRUE);
157 GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_DECODER_SINK_PAD (self));
158 }
159
160 static gboolean
gst_openexr_dec_start(GstVideoDecoder * decoder)161 gst_openexr_dec_start (GstVideoDecoder * decoder)
162 {
163 GstOpenEXRDec *self = GST_OPENEXR_DEC (decoder);
164
165 GST_DEBUG_OBJECT (self, "Starting");
166
167 return TRUE;
168 }
169
170 static gboolean
gst_openexr_dec_stop(GstVideoDecoder * video_decoder)171 gst_openexr_dec_stop (GstVideoDecoder * video_decoder)
172 {
173 GstOpenEXRDec *self = GST_OPENEXR_DEC (video_decoder);
174
175 GST_DEBUG_OBJECT (self, "Stopping");
176
177 if (self->output_state) {
178 gst_video_codec_state_unref (self->output_state);
179 self->output_state = NULL;
180 }
181
182 if (self->input_state) {
183 gst_video_codec_state_unref (self->input_state);
184 self->input_state = NULL;
185 }
186
187 GST_DEBUG_OBJECT (self, "Stopped");
188
189 return TRUE;
190 }
191
192 static GstFlowReturn
gst_openexr_dec_parse(GstVideoDecoder * decoder,GstVideoCodecFrame * frame,GstAdapter * adapter,gboolean at_eos)193 gst_openexr_dec_parse (GstVideoDecoder * decoder,
194 GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos)
195 {
196 guint8 data[8];
197 gsize size, parsed_size;
198 guint32 magic, flags;
199 gssize offset;
200
201 size = gst_adapter_available (adapter);
202
203 parsed_size = gst_video_decoder_get_pending_frame_size (decoder);
204
205 GST_DEBUG_OBJECT (decoder, "Parsing OpenEXR image data %" G_GSIZE_FORMAT,
206 size);
207
208 if (parsed_size == 0 && size < 8)
209 goto need_more_data;
210
211 /* If we did not parse anything yet, check if the frame starts with the header */
212 if (parsed_size == 0) {
213 gst_adapter_copy (adapter, data, 0, 8);
214
215 magic = GST_READ_UINT32_LE (data);
216 flags = GST_READ_UINT32_LE (data + 4);
217 if (magic != 0x01312f76 || ((flags & 0xff) != 1 && (flags & 0xff) != 2) || ((flags & 0x200) && (flags & 0x1800))) {
218 offset = gst_adapter_masked_scan_uint32_peek (adapter, 0xffffffff, 0x762f3101, 0, size, NULL);
219 if (offset == -1) {
220 gst_adapter_flush (adapter, size - 4);
221 goto need_more_data;
222 }
223
224 /* come back into this function after flushing some data */
225 gst_adapter_flush (adapter, offset);
226 goto need_more_data;
227 }
228 } else {
229 }
230
231 /* valid header, now let's try to find the next one unless we're EOS */
232 if (!at_eos) {
233 gboolean found = FALSE;
234
235 while (!found) {
236 offset = gst_adapter_masked_scan_uint32_peek (adapter, 0xffffffff, 0x762f3101, 8, size - 8 - 4, NULL);
237 if (offset == -1) {
238 gst_video_decoder_add_to_frame (decoder, size - 7);
239 goto need_more_data;
240 }
241
242 gst_adapter_copy (adapter, data, offset, 8);
243 magic = GST_READ_UINT32_LE (data);
244 flags = GST_READ_UINT32_LE (data + 4);
245 if (magic == 0x01312f76 && ((flags & 0xff) == 1 || (flags & 0xff) == 2) && (!(flags & 0x200) || !(flags & 0x1800)))
246 found = TRUE;
247 }
248 size = offset;
249 }
250
251 GST_DEBUG_OBJECT (decoder, "Have complete image of size %" G_GSSIZE_FORMAT, size + parsed_size);
252
253 gst_video_decoder_add_to_frame (decoder, size);
254
255 return gst_video_decoder_have_frame (decoder);
256
257 need_more_data:
258 GST_DEBUG_OBJECT (decoder, "Need more data");
259 return GST_VIDEO_DECODER_FLOW_NEED_DATA;
260 }
261
262 static gboolean
gst_openexr_dec_set_format(GstVideoDecoder * decoder,GstVideoCodecState * state)263 gst_openexr_dec_set_format (GstVideoDecoder * decoder,
264 GstVideoCodecState * state)
265 {
266 GstOpenEXRDec *self = GST_OPENEXR_DEC (decoder);
267
268 GST_DEBUG_OBJECT (self, "Setting format: %" GST_PTR_FORMAT, state->caps);
269
270 if (self->input_state)
271 gst_video_codec_state_unref (self->input_state);
272 self->input_state = gst_video_codec_state_ref (state);
273
274 return TRUE;
275 }
276
277 static GstFlowReturn
gst_openexr_dec_negotiate(GstOpenEXRDec * self,RgbaInputFile * file)278 gst_openexr_dec_negotiate (GstOpenEXRDec * self, RgbaInputFile * file)
279 {
280 GstVideoFormat format;
281 gint width, height;
282 gfloat par;
283
284 /* TODO: Use displayWindow here and also support output of ARGB_F16 */
285 format = GST_VIDEO_FORMAT_ARGB64;
286 Box2i dw = file->dataWindow ();
287 width = dw.max.x - dw.min.x + 1;
288 height = dw.max.y - dw.min.y + 1;
289 par = file->pixelAspectRatio ();
290
291 if (!self->output_state ||
292 self->output_state->info.finfo->format != format ||
293 self->output_state->info.width != width ||
294 self->output_state->info.height != height) {
295 if (self->output_state)
296 gst_video_codec_state_unref (self->output_state);
297 self->output_state =
298 gst_video_decoder_set_output_state (GST_VIDEO_DECODER (self), format,
299 width, height, self->input_state);
300
301 GST_DEBUG_OBJECT (self, "Have image of size %dx%d (par %f)", width, height, par);
302 gst_util_double_to_fraction (par, &self->output_state->info.par_n, &self->output_state->info.par_d);
303
304 if (!gst_video_decoder_negotiate (GST_VIDEO_DECODER (self)))
305 return GST_FLOW_NOT_NEGOTIATED;
306 }
307
308 return GST_FLOW_OK;
309 }
310
311 static GstFlowReturn
gst_openexr_dec_handle_frame(GstVideoDecoder * decoder,GstVideoCodecFrame * frame)312 gst_openexr_dec_handle_frame (GstVideoDecoder * decoder,
313 GstVideoCodecFrame * frame)
314 {
315 GstOpenEXRDec *self = GST_OPENEXR_DEC (decoder);
316 GstFlowReturn ret = GST_FLOW_OK;
317 gint64 deadline;
318 GstMapInfo map;
319 GstVideoFrame vframe;
320
321 GST_DEBUG_OBJECT (self, "Handling frame");
322
323 deadline = gst_video_decoder_get_max_decode_time (decoder, frame);
324 if (deadline < 0) {
325 GST_LOG_OBJECT (self, "Dropping too late frame: deadline %" G_GINT64_FORMAT,
326 deadline);
327 ret = gst_video_decoder_drop_frame (decoder, frame);
328 return ret;
329 }
330
331 if (!gst_buffer_map (frame->input_buffer, &map, GST_MAP_READ)) {
332 gst_video_codec_frame_unref (frame);
333
334 GST_ELEMENT_ERROR (self, CORE, FAILED,
335 ("Failed to map input buffer"), (NULL));
336 return GST_FLOW_ERROR;
337 }
338
339 /* Now read the file and catch any exceptions */
340 MemIStream *istr;
341 RgbaInputFile *file;
342 try {
343 istr =
344 new
345 MemIStream (gst_pad_get_stream_id (GST_VIDEO_DECODER_SINK_PAD
346 (decoder)), map.data, map.size);
347 }
348 catch (Iex::BaseExc& e) {
349 gst_video_codec_frame_unref (frame);
350
351 GST_ELEMENT_ERROR (self, CORE, FAILED,
352 ("Failed to create input stream"), (NULL));
353 return GST_FLOW_ERROR;
354 }
355 try {
356 file = new RgbaInputFile (*istr);
357 }
358 catch (Iex::BaseExc& e) {
359 delete istr;
360 gst_video_codec_frame_unref (frame);
361
362 GST_ELEMENT_ERROR (self, CORE, FAILED,
363 ("Failed to read OpenEXR stream"), (NULL));
364 return GST_FLOW_ERROR;
365 }
366
367 ret = gst_openexr_dec_negotiate (self, file);
368 if (ret != GST_FLOW_OK) {
369 delete file;
370 delete istr;
371 gst_buffer_unmap (frame->input_buffer, &map);
372 gst_video_codec_frame_unref (frame);
373
374 GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
375 ("Failed to negotiate"), (NULL));
376 return ret;
377 }
378
379 ret = gst_video_decoder_allocate_output_frame (decoder, frame);
380 if (ret != GST_FLOW_OK) {
381 delete file;
382 delete istr;
383 gst_buffer_unmap (frame->input_buffer, &map);
384 gst_video_codec_frame_unref (frame);
385
386 GST_ELEMENT_ERROR (self, CORE, FAILED,
387 ("Failed to allocate output buffer"), (NULL));
388 return ret;
389 }
390
391 if (!gst_video_frame_map (&vframe, &self->output_state->info,
392 frame->output_buffer, GST_MAP_WRITE)) {
393 delete file;
394 delete istr;
395 gst_buffer_unmap (frame->input_buffer, &map);
396 gst_video_codec_frame_unref (frame);
397
398 GST_ELEMENT_ERROR (self, CORE, FAILED,
399 ("Failed to map output buffer"), (NULL));
400 return GST_FLOW_ERROR;
401 }
402
403 /* Decode the file */
404 Box2i dw = file->dataWindow ();
405 int width = dw.max.x - dw.min.x + 1;
406 int height = dw.max.y - dw.min.y + 1;
407 Rgba *fb = new Rgba[width * height];
408
409 try {
410 file->setFrameBuffer (fb - dw.min.x - dw.min.y * width, 1, width);
411 file->readPixels (dw.min.y, dw.max.y);
412 } catch (Iex::BaseExc& e) {
413 delete[](fb);
414 delete file;
415 delete istr;
416 gst_buffer_unmap (frame->input_buffer, &map);
417 gst_video_frame_unmap (&vframe);
418
419 GST_ELEMENT_ERROR (self, CORE, FAILED, ("Failed to read pixels"), (NULL));
420 return GST_FLOW_ERROR;
421 }
422
423 /* And convert from ARGB64_F16 to ARGB64 */
424 gint i, j;
425 guint16 *dest = (guint16 *) GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0);
426 guint dstride = GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0);
427 Rgba *ptr = fb;
428
429 /* TODO: Use displayWindow here and also support output of ARGB_F16
430 * and add a conversion filter element that can change exposure and
431 * other things */
432 for (i = 0; i < height; i++) {
433 for (j = 0; j < width; j++) {
434 dest[4 * j + 0] = CLAMP (((float) ptr->a) * 65536, 0, 65535);
435 dest[4 * j + 1] = CLAMP (((float) ptr->r) * 65536, 0, 65535);
436 dest[4 * j + 2] = CLAMP (((float) ptr->g) * 65536, 0, 65535);
437 dest[4 * j + 3] = CLAMP (((float) ptr->b) * 65536, 0, 65535);
438 ptr++;
439 }
440 dest += dstride / 2;
441 }
442
443 delete[](fb);
444 delete file;
445 delete istr;
446 gst_buffer_unmap (frame->input_buffer, &map);
447 gst_video_frame_unmap (&vframe);
448
449 ret = gst_video_decoder_finish_frame (decoder, frame);
450
451 return ret;
452 }
453
454 static gboolean
gst_openexr_dec_decide_allocation(GstVideoDecoder * decoder,GstQuery * query)455 gst_openexr_dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query)
456 {
457 GstBufferPool *pool;
458 GstStructure *config;
459
460 if (!GST_VIDEO_DECODER_CLASS (parent_class)->decide_allocation (decoder,
461 query))
462 return FALSE;
463
464 g_assert (gst_query_get_n_allocation_pools (query) > 0);
465 gst_query_parse_nth_allocation_pool (query, 0, &pool, NULL, NULL, NULL);
466 g_assert (pool != NULL);
467
468 config = gst_buffer_pool_get_config (pool);
469 if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL)) {
470 gst_buffer_pool_config_add_option (config,
471 GST_BUFFER_POOL_OPTION_VIDEO_META);
472 }
473 gst_buffer_pool_set_config (pool, config);
474 gst_object_unref (pool);
475
476 return TRUE;
477 }
478