Initial version of libomxil-vc4 for RPI3
[platform/adaptation/broadcom/libomxil-vc4.git] / containers / simple / simple_reader.c
1 /*
2 Copyright (c) 2012, Broadcom Europe Ltd
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7     * Redistributions of source code must retain the above copyright
8       notice, this list of conditions and the following disclaimer.
9     * Redistributions in binary form must reproduce the above copyright
10       notice, this list of conditions and the following disclaimer in the
11       documentation and/or other materials provided with the distribution.
12     * Neither the name of the copyright holder nor the
13       names of its contributors may be used to endorse or promote products
14       derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "containers/core/containers_private.h"
32 #include "containers/core/containers_io_helpers.h"
33 #include "containers/core/containers_utils.h"
34 #include "containers/core/containers_logging.h"
35
36 #include "simple_common.h"
37
38 /******************************************************************************
39 Defines.
40 ******************************************************************************/
41 #define MAX_LINE_SIZE 512
42 #define LINE_PADDING 3 /* 2 for newline + 1 for null */
43
44 #define MAX_TRACKS 4
45 #define MAX_HEADER_LINES 512
46
47 typedef enum SIMPLE_VARIANT_T
48 {
49    VARIANT_DEFAULT = 0,
50    VARIANT_MMAL,
51    VARIANT_OMX
52 } SIMPLE_VARIANT_T;
53
54 /******************************************************************************
55 Type definitions
56 ******************************************************************************/
57 typedef struct SIMPLE_PACKET_STATE_T
58 {
59    unsigned int track_num;
60    unsigned int flags;
61
62    uint64_t metadata_offset; /* Offset in metadata stream */
63    uint32_t data_size;       /* Size of current data packet */
64    uint32_t data_left;       /* Data left to read in current packet */
65
66    int64_t pts;
67
68 } SIMPLE_PACKET_STATE_T;
69
70 typedef struct VC_CONTAINER_TRACK_MODULE_T
71 {
72    SIMPLE_PACKET_STATE_T *state;
73    SIMPLE_PACKET_STATE_T local_state;
74
75    VC_CONTAINER_IO_T *io;
76    uint64_t data_offset;     /* Current offset in data stream */
77    char uri[MAX_LINE_SIZE+1];
78
79    SIMPLE_VARIANT_T variant;
80
81 } VC_CONTAINER_TRACK_MODULE_T;
82
83 typedef struct VC_CONTAINER_MODULE_T
84 {
85    VC_CONTAINER_TRACK_T *tracks[MAX_TRACKS];
86
87    char line[MAX_LINE_SIZE + LINE_PADDING];
88
89    int64_t metadata_offset;
90
91    /* Shared packet state. This is used when the tracks are in sync,
92     * and for the track at the earliest position in the file when they are
93     * not in sync */
94    SIMPLE_PACKET_STATE_T state;
95
96 } VC_CONTAINER_MODULE_T;
97
98 /******************************************************************************
99 Function prototypes
100 ******************************************************************************/
101 VC_CONTAINER_STATUS_T simple_reader_open( VC_CONTAINER_T * );
102
103 /******************************************************************************
104 Local Functions
105 ******************************************************************************/
106 static VC_CONTAINER_STATUS_T simple_read_line( VC_CONTAINER_T *ctx )
107 {
108    VC_CONTAINER_MODULE_T *module = ctx->priv->module;
109    unsigned int i, bytes = PEEK_BYTES(ctx, module->line, sizeof(module->line)-1);
110
111    if (!bytes)
112       return VC_CONTAINER_ERROR_EOS;
113
114    /* Find new-line marker */
115    for (i = 0; i < bytes; i++)
116       if (module->line[i] == '\n')
117          break;
118
119    /* Bail out if line is bigger than the maximum allowed */
120    if (i == sizeof(module->line)-1)
121    {
122       LOG_ERROR(ctx, "line too big");
123       return VC_CONTAINER_ERROR_CORRUPTED;
124    }
125
126    if (i < bytes)
127    {
128       module->line[i++] = 0;
129       if (i < bytes && module->line[i] == '\r')
130          i++;
131    }
132    module->line[i] = 0; /* Make sure the line is null terminated */
133
134    SKIP_BYTES(ctx, i);
135    return VC_CONTAINER_SUCCESS;
136 }
137
138 static VC_CONTAINER_STATUS_T simple_read_header( VC_CONTAINER_T *ctx )
139 {
140    VC_CONTAINER_MODULE_T *module = ctx->priv->module;
141    VC_CONTAINER_TRACK_T *track = NULL;
142    VC_CONTAINER_FOURCC_T fourcc;
143    int matches, width, height, channels, samplerate, bps, blockalign, value;
144    unsigned int lines = 1;
145
146    /* Skip the signature */
147    if (simple_read_line(ctx) != VC_CONTAINER_SUCCESS)
148       return VC_CONTAINER_ERROR_CORRUPTED;
149
150    while (lines++ < MAX_HEADER_LINES &&
151           simple_read_line(ctx) == VC_CONTAINER_SUCCESS)
152    {
153       /* Our exit condition is the end signature */
154       if (!memcmp(module->line, SIGNATURE_END_STRING, sizeof(SIGNATURE_STRING)-1))
155       {
156          if (track) ctx->tracks[ctx->tracks_num++] = track;
157          return VC_CONTAINER_SUCCESS;
158       }
159
160       /* Start of track description */
161       if (!memcmp(module->line, "TRACK ", sizeof("TRACK ")-1))
162       {
163          /* Add track we were constructing */
164          if (track) ctx->tracks[ctx->tracks_num++] = track;
165          track = NULL;
166
167          if (ctx->tracks_num >= MAX_TRACKS)
168          {
169             LOG_ERROR(ctx, "too many tracks, ignoring: %s", module->line);
170             continue;
171          }
172          track = vc_container_allocate_track(ctx, sizeof(*track->priv->module));
173          if (!track)
174             return VC_CONTAINER_ERROR_OUT_OF_MEMORY;
175
176          track->is_enabled = true;
177          track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
178
179          if ((matches = sscanf(module->line,
180                  "TRACK video, %4c, %i, %i",
181                  (char *)&fourcc, &width, &height)) > 0)
182          {
183             track->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO;
184             track->format->codec = fourcc;
185             if (matches > 1) track->format->type->video.width = width;
186             if (matches > 2) track->format->type->video.height = height;
187          }
188          else if ((matches = sscanf(module->line,
189                  "TRACK audio, %4c, %i, %i, %i, %i",
190                  (char *)&fourcc, &channels, &samplerate, &bps,
191                  &blockalign)) > 0)
192          {
193             track->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO;
194             track->format->codec = fourcc;
195             if (matches > 1) track->format->type->audio.channels = channels;
196             if (matches > 2) track->format->type->audio.sample_rate = samplerate;
197             if (matches > 3) track->format->type->audio.bits_per_sample = bps;
198             if (matches > 4) track->format->type->audio.block_align = blockalign;
199          }
200          if ((matches = sscanf(module->line,
201                  "TRACK subpicture, %4c, %i",
202                  (char *)&fourcc, &value)) > 0)
203          {
204             track->format->es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE;
205             track->format->codec = fourcc;
206             if (matches > 1) track->format->type->subpicture.encoding = value;
207          }
208       }
209
210       if (!track)
211          continue; /* Nothing interesting */
212
213       /* VARIANT of the syntax */
214       if (sscanf(module->line, CONFIG_VARIANT" %i", &value) == 1)
215       {
216          track->priv->module->variant = value;
217          LOG_FORMAT(ctx, CONFIG_VARIANT": %i", value);
218       }
219       /* URI for elementary stream */
220       else if (sscanf(module->line, CONFIG_URI" %s", track->priv->module->uri) == 1)
221          LOG_FORMAT(ctx, CONFIG_URI": %s", track->priv->module->uri);
222       /* COCDEC_VARIANT of elementary stream */
223       else if (sscanf(module->line, CONFIG_CODEC_VARIANT" %4c", (char *)&fourcc) == 1)
224       {
225          track->format->codec_variant = fourcc;
226          LOG_FORMAT(ctx, CONFIG_CODEC_VARIANT": %4.4s", (char *)&fourcc);
227       }
228       /* BITRATE of elementary stream */
229       else if (sscanf(module->line, CONFIG_BITRATE" %i", &value) == 1)
230       {
231          track->format->bitrate = value;
232          LOG_FORMAT(ctx, CONFIG_BITRATE": %i", value);
233       }
234       /* UNFRAMED elementary stream */
235       else if (!memcmp(module->line, CONFIG_UNFRAMED, sizeof(CONFIG_UNFRAMED)-1))
236       {
237          track->format->flags &= ~VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
238          LOG_FORMAT(ctx, CONFIG_UNFRAMED);
239       }
240       /* VIDEO_CROP information */
241       else if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO &&
242          sscanf(module->line, CONFIG_VIDEO_CROP" %i, %i", &width, &height) == 2)
243       {
244          track->format->type->video.visible_width = width;
245          track->format->type->video.visible_height = height;
246          LOG_FORMAT(ctx, CONFIG_VIDEO_CROP": %i, %i", width, height);
247       }
248       /* VIDEO_ASPECT information */
249       else if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO &&
250          sscanf(module->line, CONFIG_VIDEO_ASPECT" %i, %i", &width, &height) == 2)
251       {
252          track->format->type->video.par_num = width;
253          track->format->type->video.par_den = height;
254          LOG_FORMAT(ctx, CONFIG_VIDEO_ASPECT": %i, %i", width, height);
255       }
256    }
257
258    if (track) vc_container_free_track(ctx, track);
259    return VC_CONTAINER_ERROR_CORRUPTED;
260 }
261
262 static uint32_t simple_convert_packet_flags(VC_CONTAINER_T *ctx,
263    unsigned int track_num, uint32_t flags)
264 {
265    typedef struct { uint32_t from; uint32_t to; } convert_from_t;
266    const convert_from_t convert_from_mmal[] =
267    { {1<<1, VC_CONTAINER_PACKET_FLAG_FRAME_START},
268      {1<<2, VC_CONTAINER_PACKET_FLAG_FRAME_END},
269      {1<<3, VC_CONTAINER_PACKET_FLAG_KEYFRAME},
270      {1<<4, VC_CONTAINER_PACKET_FLAG_DISCONTINUITY},
271      {1<<5, VC_CONTAINER_PACKET_FLAG_CONFIG},
272      {1<<6, VC_CONTAINER_PACKET_FLAG_ENCRYPTED},
273      {0, 0} };
274    const convert_from_t convert_from_omx[] =
275    { {0x10, VC_CONTAINER_PACKET_FLAG_FRAME_END},
276      {0x20, VC_CONTAINER_PACKET_FLAG_KEYFRAME},
277      {0x80, VC_CONTAINER_PACKET_FLAG_CONFIG},
278      {0, 0} };
279    const convert_from_t *convert_from = NULL;
280    int i;
281
282    switch (ctx->tracks[track_num]->priv->module->variant)
283    {
284       case VARIANT_MMAL: convert_from = convert_from_mmal; break;
285       case VARIANT_OMX: convert_from = convert_from_omx; break;
286       default: break;
287    }
288
289    if (convert_from)
290    {
291       uint32_t new_flags = 0;
292       for (i = 0; convert_from[i].from; i++)
293          if (convert_from[i].from & flags)
294             new_flags |= convert_from[i].to;
295       return new_flags;
296    }
297
298    return flags;
299 }
300
301 static int64_t simple_convert_packet_pts(VC_CONTAINER_T *ctx,
302    unsigned int track_num, int64_t pts, uint32_t flags)
303 {
304    if (ctx->tracks[track_num]->priv->module->variant == VARIANT_OMX &&
305        flags & 0x100)
306       return VC_CONTAINER_TIME_UNKNOWN;
307
308    return pts;
309 }
310
311 /*****************************************************************************
312 Functions exported as part of the Container Module API
313  *****************************************************************************/
314 static VC_CONTAINER_STATUS_T simple_reader_read( VC_CONTAINER_T *ctx,
315    VC_CONTAINER_PACKET_T *packet, uint32_t flags )
316 {
317    VC_CONTAINER_MODULE_T *module = ctx->priv->module;
318    VC_CONTAINER_TRACK_MODULE_T *track_module;
319    VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
320    SIMPLE_PACKET_STATE_T *state;
321
322    /* If a specific track has been selected, use the track packet state */
323    if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK)
324       state = ctx->tracks[packet->track]->priv->module->state;
325    else
326       state = &module->state;
327
328    /* Switch to the next packet when the current one is empty */
329    if (!state->data_left)
330    {
331       unsigned int track_num, size;
332       int64_t pts;
333       int flags;
334
335       SEEK(ctx, state->metadata_offset);
336       status = simple_read_line(ctx);
337       if (status != VC_CONTAINER_SUCCESS)
338          return status;
339
340       if (sscanf(module->line, "%u %u %"PRIi64" %i",
341              &track_num, &size, &pts, &flags) != 4 &&
342            (track_num = 0, sscanf(module->line, "%u %"PRIi64" %i",
343              &size, &pts, &flags)) != 3)
344       {
345          LOG_ERROR(ctx, "invalid metadata: %s", module->line);
346          return VC_CONTAINER_ERROR_CORRUPTED;
347       }
348       state->metadata_offset = STREAM_POSITION(ctx);
349
350       if (track_num >= ctx->tracks_num)
351       {
352          LOG_DEBUG(ctx, "skipping %i bytes for track %d/%d",
353             size, track_num, ctx->tracks_num);
354          return VC_CONTAINER_ERROR_CONTINUE;
355       }
356
357       /* If we are reading from the global state (i.e. normal read or forced
358          read from the track on the global state), and the track we found is
359          not on the global state, reconnect the two */
360       if (state == &module->state &&
361          ctx->tracks[track_num]->priv->module->state != &module->state)
362       {
363          LOG_DEBUG(ctx, "reconnect track %u to the global state", track_num);
364          ctx->tracks[track_num]->priv->module->state = &module->state;
365          module->state = ctx->tracks[track_num]->priv->module->local_state;
366          return VC_CONTAINER_ERROR_CONTINUE;
367       }
368
369       state->data_size = state->data_left = size;
370       state->track_num = track_num;
371       state->flags = simple_convert_packet_flags(ctx, track_num, flags);
372       state->pts = simple_convert_packet_pts(ctx, track_num, pts, flags);
373
374       /* Discard empty packets */
375       if (!state->data_size && !state->flags)
376          return VC_CONTAINER_ERROR_CONTINUE;
377    }
378
379    /* If there is data from another track skip past it */
380    if ((flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) &&
381        state->track_num != packet->track)
382    {
383       LOG_DEBUG(ctx, "skipping track %d/%d as we are ignoring it",
384          state->track_num, ctx->tracks_num);
385
386       track_module = ctx->tracks[packet->track]->priv->module;
387
388       /* Handle disconnection from global state */
389       if (state == &module->state &&
390          ctx->tracks[state->track_num]->priv->module->state == &module->state)
391       {
392          /* Make a copy of the global state */
393          LOG_DEBUG(ctx, "using local state on track %d", packet->track);
394          track_module->local_state = module->state;
395          track_module->state = &track_module->local_state;
396       }
397
398       track_module->state->data_left = 0;
399       return VC_CONTAINER_ERROR_CONTINUE;
400    }
401
402    /*
403     * From this point we know we have the packet which was requested
404     */
405
406    /* !!!! If we aren't in the right position in the file go there now. */
407
408    track_module = ctx->tracks[state->track_num]->priv->module;
409    packet->track = state->track_num;
410    packet->size = state->data_left;
411    packet->frame_size = (state->flags & VC_CONTAINER_PACKET_FLAG_FRAME) ?
412       state->data_size : 0;
413    packet->flags = state->flags;
414    packet->pts = state->pts;
415    packet->dts = VC_CONTAINER_TIME_UNKNOWN;
416    if (state->data_left != state->data_size)
417       packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_START;
418
419    if (flags & VC_CONTAINER_READ_FLAG_SKIP)
420    {
421       track_module->data_offset += state->data_left;
422       state->data_left = 0;
423       return VC_CONTAINER_SUCCESS;
424    }
425
426    if (flags & VC_CONTAINER_READ_FLAG_INFO)
427    {
428       return VC_CONTAINER_SUCCESS;
429    }
430
431    /* Now try to read data into buffer */
432    vc_container_io_seek(track_module->io, track_module->data_offset);
433
434    packet->size = vc_container_io_read(track_module->io, packet->data,
435       MIN(packet->buffer_size, state->data_left));
436    state->data_left -= packet->size;
437    track_module->data_offset += packet->size;
438
439    if (state->data_left)
440       packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END;
441
442    return track_module->io->status;
443 }
444
445 /*****************************************************************************/
446 static VC_CONTAINER_STATUS_T simple_reader_seek( VC_CONTAINER_T *ctx, int64_t *offset,
447    VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags)
448 {
449    VC_CONTAINER_PARAM_UNUSED(ctx);
450    VC_CONTAINER_PARAM_UNUSED(offset);
451    VC_CONTAINER_PARAM_UNUSED(mode);
452    VC_CONTAINER_PARAM_UNUSED(flags);
453    return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION;
454 }
455
456 /*****************************************************************************/
457 static VC_CONTAINER_STATUS_T simple_reader_close( VC_CONTAINER_T *ctx )
458 {
459    VC_CONTAINER_MODULE_T *module = ctx->priv->module;
460
461    for (; ctx->tracks_num > 0; ctx->tracks_num--)
462    {
463       VC_CONTAINER_TRACK_T *track = ctx->tracks[ctx->tracks_num-1];
464       if (track->priv->module->io)
465          vc_container_io_close(track->priv->module->io);
466       vc_container_free_track(ctx, track);
467    }
468
469    free(module);
470    return VC_CONTAINER_SUCCESS;
471 }
472
473 /*****************************************************************************/
474 VC_CONTAINER_STATUS_T simple_reader_open( VC_CONTAINER_T *ctx )
475 {
476    VC_CONTAINER_MODULE_T *module = 0;
477    VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID;
478    uint8_t h[sizeof(SIGNATURE_STRING)];
479    unsigned int i;
480
481    /* Check for the signature */
482    if (PEEK_BYTES(ctx, h, sizeof(h)) != sizeof(h) ||
483       memcmp(h, SIGNATURE_STRING, sizeof(SIGNATURE_STRING)-1))
484       return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
485
486    LOG_DEBUG(ctx, "using simple reader");
487
488    /* Allocate our context */
489    module = malloc(sizeof(*module));
490    if (!module) return VC_CONTAINER_ERROR_OUT_OF_MEMORY;
491    memset(module, 0, sizeof(*module));
492    ctx->priv->module = module;
493    ctx->tracks = module->tracks;
494
495    status = simple_read_header(ctx);
496    if (status != VC_CONTAINER_SUCCESS)
497       goto error;
498
499    /* Open all the elementary streams */
500    for (i = 0; i < ctx->tracks_num; i++)
501    {
502       VC_CONTAINER_TRACK_T *track = ctx->tracks[i];
503       char *uri;
504
505       track->priv->module->io = vc_container_io_open(track->priv->module->uri,
506          VC_CONTAINER_IO_MODE_READ, &status);
507
508       /* URI might be relative to the path of the metadata file so
509        * try again with that new path */
510       if (!track->priv->module->io &&
511           (uri = malloc(strlen(ctx->priv->io->uri) +
512               strlen(track->priv->module->uri) + 1)) != NULL)
513       {
514          char *end;
515
516          strcpy(uri, ctx->priv->io->uri);
517
518          /* Find the last directory separator */
519          for (end = uri + strlen(ctx->priv->io->uri) + 1; end != uri; end--)
520             if (*(end-1) == '/' || *(end-1) == '\\')
521                break;
522          strcpy(end, track->priv->module->uri);
523
524          track->priv->module->io = vc_container_io_open(uri,
525             VC_CONTAINER_IO_MODE_READ, &status);
526          if (!track->priv->module->io)
527             LOG_ERROR(ctx, "could not open elementary stream: %s", uri);
528          free(uri);
529       }
530       if (!track->priv->module->io)
531       {
532          LOG_ERROR(ctx, "could not open elementary stream: %s",
533             track->priv->module->uri);
534          goto error;
535       }
536    }
537
538    /*
539     *  We now have all the information we really need to start playing the stream
540     */
541
542    module->metadata_offset = STREAM_POSITION(ctx);
543
544    /* Initialise state for all tracks */
545    module->state.metadata_offset = module->metadata_offset;
546    for (i = 0; i < ctx->tracks_num; i++)
547    {
548       VC_CONTAINER_TRACK_T *track = ctx->tracks[i];
549       track->priv->module->state = &module->state;
550    }
551
552    /* Look for the codec configuration data for each track so
553     * we can store it in the track format */
554    for (i = 0; i < ctx->tracks_num; i++)
555    {
556       VC_CONTAINER_TRACK_T *track = ctx->tracks[i];
557       VC_CONTAINER_PACKET_T packet;
558       packet.track = i;
559       status = VC_CONTAINER_ERROR_CONTINUE;
560
561       while (status == VC_CONTAINER_ERROR_CONTINUE)
562          status = simple_reader_read(ctx, &packet,
563             VC_CONTAINER_READ_FLAG_INFO | VC_CONTAINER_READ_FLAG_FORCE_TRACK);
564       if (status != VC_CONTAINER_SUCCESS)
565          continue;
566
567       status = vc_container_track_allocate_extradata(ctx, track, packet.size);
568       if (status != VC_CONTAINER_SUCCESS)
569          continue;
570
571       packet.data = track->format->extradata;
572       packet.buffer_size = packet.size;
573       packet.size = 0;
574       status = simple_reader_read(ctx, &packet,
575          VC_CONTAINER_READ_FLAG_FORCE_TRACK);
576       if (status != VC_CONTAINER_SUCCESS)
577          continue;
578
579       track->format->extradata_size = packet.size;
580    }
581
582    ctx->priv->pf_close = simple_reader_close;
583    ctx->priv->pf_read = simple_reader_read;
584    ctx->priv->pf_seek = simple_reader_seek;
585    return VC_CONTAINER_SUCCESS;
586
587  error:
588    LOG_ERROR(ctx, "simple: error opening stream (%i)", status);
589    simple_reader_close(ctx);
590    return status;
591 }
592
593 /********************************************************************************
594  Entrypoint function
595  ********************************************************************************/
596
597 #if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__)
598 # pragma weak reader_open simple_reader_open
599 #endif