| 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 |  | 
| 28 | #include <stdlib.h> | 
| 29 | #include <string.h> | 
| 30 | #include <stdio.h> | 
| 31 |  | 
| 32 | #include "containers/core/containers_private.h" | 
| 33 | #include "containers/core/containers_loader.h" | 
| 34 |  | 
| 35 | #if !defined(ENABLE_CONTAINERS_STANDALONE) | 
| 36 |    #include "vcos_dlfcn.h" | 
| 37 |    #define DL_SUFFIX VCOS_SO_EXT | 
| 38 |    #ifndef DL_PATH_PREFIX | 
| 39 |       #define DL_PATH_PREFIX "" | 
| 40 |    #endif | 
| 41 | #endif | 
| 42 |  | 
| 43 | /****************************************************************************** | 
| 44 | Type definitions. | 
| 45 | ******************************************************************************/ | 
| 46 |  | 
| 47 | typedef VC_CONTAINER_STATUS_T (*VC_CONTAINER_READER_OPEN_FUNC_T)(VC_CONTAINER_T *); | 
| 48 | typedef VC_CONTAINER_STATUS_T (*VC_CONTAINER_WRITER_OPEN_FUNC_T)(VC_CONTAINER_T *); | 
| 49 |  | 
| 50 | /****************************************************************************** | 
| 51 | Prototypes for local functions | 
| 52 | ******************************************************************************/ | 
| 53 |  | 
| 54 | static void reset_context(VC_CONTAINER_T *p_ctx); | 
| 55 | static VC_CONTAINER_READER_OPEN_FUNC_T load_library(void **handle, const char *name, const char *ext, int read); | 
| 56 | static void unload_library(void *handle); | 
| 57 | static VC_CONTAINER_READER_OPEN_FUNC_T load_reader(void **handle, const char *name); | 
| 58 | static VC_CONTAINER_READER_OPEN_FUNC_T load_writer(void **handle, const char *name); | 
| 59 | static VC_CONTAINER_READER_OPEN_FUNC_T load_metadata_reader(void **handle, const char *name); | 
| 60 | static const char* container_for_fileext(const char *fileext); | 
| 61 |  | 
| 62 | /******************************************************************************** | 
| 63 |  List of supported containers | 
| 64 |  ********************************************************************************/ | 
| 65 |  | 
| 66 | static const char *readers[] = | 
| 67 | {"mp4" , "asf" , "avi" , "mkv" , "wav" , "flv" , "simple" , "rawvideo" , "mpga" , "ps" , "rtp" , "rtsp" , "rcv" , "rv9" , "qsynth" , "binary" , 0}; | 
| 68 | static const char *writers[] = | 
| 69 | {"mp4" , "asf" , "avi" , "binary" , "simple" , "rawvideo" , 0}; | 
| 70 | static const char *metadata_readers[] = | 
| 71 | {"id3" , 0}; | 
| 72 |  | 
| 73 | #if defined(ENABLE_CONTAINERS_STANDALONE) | 
| 74 | VC_CONTAINER_STATUS_T asf_reader_open( VC_CONTAINER_T * ); | 
| 75 | VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T * ); | 
| 76 | VC_CONTAINER_STATUS_T avi_writer_open( VC_CONTAINER_T * ); | 
| 77 | VC_CONTAINER_STATUS_T mp4_reader_open( VC_CONTAINER_T * ); | 
| 78 | VC_CONTAINER_STATUS_T mp4_writer_open( VC_CONTAINER_T * ); | 
| 79 | VC_CONTAINER_STATUS_T mpga_reader_open( VC_CONTAINER_T * ); | 
| 80 | VC_CONTAINER_STATUS_T mkv_reader_open( VC_CONTAINER_T * ); | 
| 81 | VC_CONTAINER_STATUS_T wav_reader_open( VC_CONTAINER_T * ); | 
| 82 | VC_CONTAINER_STATUS_T flv_reader_open( VC_CONTAINER_T * ); | 
| 83 | VC_CONTAINER_STATUS_T ps_reader_open( VC_CONTAINER_T * ); | 
| 84 | VC_CONTAINER_STATUS_T rtp_reader_open( VC_CONTAINER_T * ); | 
| 85 | VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T * ); | 
| 86 | VC_CONTAINER_STATUS_T binary_reader_open( VC_CONTAINER_T * ); | 
| 87 | VC_CONTAINER_STATUS_T binary_writer_open( VC_CONTAINER_T * ); | 
| 88 | VC_CONTAINER_STATUS_T rcv_reader_open( VC_CONTAINER_T * ); | 
| 89 | VC_CONTAINER_STATUS_T rv9_reader_open( VC_CONTAINER_T * ); | 
| 90 | VC_CONTAINER_STATUS_T qsynth_reader_open( VC_CONTAINER_T * ); | 
| 91 | VC_CONTAINER_STATUS_T simple_reader_open( VC_CONTAINER_T * ); | 
| 92 | VC_CONTAINER_STATUS_T simple_writer_open( VC_CONTAINER_T * ); | 
| 93 | VC_CONTAINER_STATUS_T rawvideo_reader_open( VC_CONTAINER_T * ); | 
| 94 | VC_CONTAINER_STATUS_T rawvideo_writer_open( VC_CONTAINER_T * ); | 
| 95 |  | 
| 96 | VC_CONTAINER_STATUS_T id3_metadata_reader_open( VC_CONTAINER_T * ); | 
| 97 |  | 
| 98 | static struct | 
| 99 | { | 
| 100 |    const char *name; | 
| 101 |    VC_CONTAINER_READER_OPEN_FUNC_T func; | 
| 102 | } reader_entry_points[] = | 
| 103 | { | 
| 104 |    {"asf" , &asf_reader_open}, | 
| 105 |    {"avi" , &avi_reader_open}, | 
| 106 |    {"mpga" , &mpga_reader_open}, | 
| 107 |    {"mkv" , &mkv_reader_open}, | 
| 108 |    {"wav" , &wav_reader_open}, | 
| 109 |    {"mp4" ,  &mp4_reader_open}, | 
| 110 |    {"flv" ,  &flv_reader_open}, | 
| 111 |    {"ps" ,  &ps_reader_open}, | 
| 112 |    {"binary" ,  &binary_reader_open}, | 
| 113 |    {"rtp" ,  &rtp_reader_open}, | 
| 114 |    {"rtsp" , &rtsp_reader_open}, | 
| 115 |    {"rcv" , &rcv_reader_open}, | 
| 116 |    {"rv9" , &rv9_reader_open}, | 
| 117 |    {"qsynth" , &qsynth_reader_open}, | 
| 118 |    {"simple" , &simple_reader_open}, | 
| 119 |    {"rawvideo" , &rawvideo_reader_open}, | 
| 120 |    {0, 0} | 
| 121 | }; | 
| 122 |  | 
| 123 | static struct | 
| 124 | { | 
| 125 |    const char *name; | 
| 126 |    VC_CONTAINER_READER_OPEN_FUNC_T func; | 
| 127 | } metadata_reader_entry_points[] = | 
| 128 | { | 
| 129 |    {"id3" , &id3_metadata_reader_open}, | 
| 130 |    {0, 0} | 
| 131 | }; | 
| 132 |  | 
| 133 | static struct | 
| 134 | { | 
| 135 |    const char *name; | 
| 136 |    VC_CONTAINER_WRITER_OPEN_FUNC_T func; | 
| 137 | } writer_entry_points[] = | 
| 138 | { | 
| 139 |    {"avi" , &avi_writer_open}, | 
| 140 |    {"mp4" , &mp4_writer_open}, | 
| 141 |    {"binary" , &binary_writer_open}, | 
| 142 |    {"simple" , &simple_writer_open}, | 
| 143 |    {"rawvideo" , &rawvideo_writer_open}, | 
| 144 |    {0, 0} | 
| 145 | }; | 
| 146 | #endif /* defined(ENABLE_CONTAINERS_STANDALONE) */ | 
| 147 |  | 
| 148 | /** Table describing the mapping between file extensions and container name. | 
| 149 |     This is only used as optimisation to decide which container to try first. | 
| 150 |     Entries where the file extension and container have the same name can be omitted. */ | 
| 151 | static const struct { | 
| 152 |    const char *extension; | 
| 153 |    const char *container; | 
| 154 | } extension_container_mapping[] = | 
| 155 | { | 
| 156 |    { "wma" ,  "asf"  }, | 
| 157 |    { "wmv" ,  "asf"  }, | 
| 158 |    { "mov" ,  "mp4"  }, | 
| 159 |    { "3gp" ,  "mp4"  }, | 
| 160 |    { "mp2" ,  "mpga"  }, | 
| 161 |    { "mp3" ,  "mpga"  }, | 
| 162 |    { "webm" , "mkv"  }, | 
| 163 |    { "mid" ,  "qsynth"  }, | 
| 164 |    { "mld" ,  "qsynth"  }, | 
| 165 |    { "mmf" ,  "qsynth"  }, | 
| 166 |    { 0, 0 } | 
| 167 | }; | 
| 168 |  | 
| 169 | /******************************************************************************** | 
| 170 |  Public functions | 
| 171 |  ********************************************************************************/ | 
| 172 | VC_CONTAINER_STATUS_T vc_container_load_reader(VC_CONTAINER_T *p_ctx, const char *fileext) | 
| 173 | { | 
| 174 |    const char *name; | 
| 175 |    void *handle = NULL; | 
| 176 |    VC_CONTAINER_READER_OPEN_FUNC_T func; | 
| 177 |    VC_CONTAINER_STATUS_T status; | 
| 178 |    unsigned int i; | 
| 179 |    int64_t offset; | 
| 180 |     | 
| 181 |    vc_container_assert(p_ctx && !p_ctx->priv->module_handle); | 
| 182 |  | 
| 183 |    /* FIXME: the missing part here is code that reads a configuration or | 
| 184 |       searches the filesystem for container libraries. Instead, we currently | 
| 185 |       rely on static arrays i.e. 'readers', 'writers', etc. */ | 
| 186 |  | 
| 187 |    /* Before trying proper container readers, iterate through metadata  | 
| 188 |       readers to parse tags concatenated to start/end of stream */ | 
| 189 |    for(i = 0; metadata_readers[i]; i++) | 
| 190 |    { | 
| 191 |       if ((func = load_metadata_reader(&handle, metadata_readers[i])) != NULL) | 
| 192 |       { | 
| 193 |          status = (*func)(p_ctx); | 
| 194 |          if(!status && p_ctx->priv->pf_close) p_ctx->priv->pf_close(p_ctx); | 
| 195 |          reset_context(p_ctx); | 
| 196 |          unload_library(handle); | 
| 197 |          if(status == VC_CONTAINER_SUCCESS) break; | 
| 198 |          if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; | 
| 199 |       } | 
| 200 |    } | 
| 201 |  | 
| 202 |    /* Store the current position, in case any containers don't leave the stream | 
| 203 |       at the start, and the IO layer can cope with the seek */ | 
| 204 |    offset = p_ctx->priv->io->offset; | 
| 205 |  | 
| 206 |    /* Now move to containers, try to find a readers using the file extension to name  | 
| 207 |       mapping first */ | 
| 208 |    if (fileext && (name = container_for_fileext(fileext)) != NULL && (func = load_reader(&handle, name)) != NULL) | 
| 209 |    { | 
| 210 |       status = (*func)(p_ctx); | 
| 211 |       if(status == VC_CONTAINER_SUCCESS) goto success; | 
| 212 |       unload_library(handle); | 
| 213 |       if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; | 
| 214 |    } | 
| 215 |  | 
| 216 |    /* If there was no suitable mapping, iterate through all readers. */ | 
| 217 |    for(i = 0; readers[i]; i++) | 
| 218 |    { | 
| 219 |       if ((func = load_reader(&handle, readers[i])) != NULL) | 
| 220 |       { | 
| 221 |          if(vc_container_io_seek(p_ctx->priv->io, offset) != VC_CONTAINER_SUCCESS) | 
| 222 |          { | 
| 223 |             unload_library(handle); | 
| 224 |             goto error; | 
| 225 |          } | 
| 226 |  | 
| 227 |          status = (*func)(p_ctx); | 
| 228 |          if(status == VC_CONTAINER_SUCCESS) goto success; | 
| 229 |          reset_context(p_ctx); | 
| 230 |          unload_library(handle); | 
| 231 |          if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; | 
| 232 |       } | 
| 233 |    } | 
| 234 |  | 
| 235 |  error: | 
| 236 |    return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; | 
| 237 |  | 
| 238 |  success: | 
| 239 |    p_ctx->priv->module_handle = handle; | 
| 240 |    return VC_CONTAINER_SUCCESS; | 
| 241 | } | 
| 242 |  | 
| 243 | /*****************************************************************************/ | 
| 244 | VC_CONTAINER_STATUS_T vc_container_load_writer(VC_CONTAINER_T *p_ctx, const char *fileext) | 
| 245 | { | 
| 246 |    const char *name; | 
| 247 |    void *handle = NULL; | 
| 248 |    VC_CONTAINER_WRITER_OPEN_FUNC_T func; | 
| 249 |    VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FAILED; | 
| 250 |    unsigned int i; | 
| 251 |     | 
| 252 |    vc_container_assert(p_ctx && !p_ctx->priv->module_handle); | 
| 253 |       | 
| 254 |    /* Do we have a container mapping for this file extension? */ | 
| 255 |    if ((name = container_for_fileext(fileext)) != NULL && (func = load_writer(&handle, name)) != NULL) | 
| 256 |    { | 
| 257 |       status = (*func)(p_ctx); | 
| 258 |       if(status == VC_CONTAINER_SUCCESS) goto success; | 
| 259 |       unload_library(handle); | 
| 260 |       if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; | 
| 261 |    } | 
| 262 |  | 
| 263 |    /* If there was no suitable mapping, iterate through all writers. */ | 
| 264 |    for(i = 0; writers[i]; i++) | 
| 265 |    { | 
| 266 |       if ((func = load_writer(&handle, writers[i])) != NULL) | 
| 267 |       { | 
| 268 |          status = (*func)(p_ctx); | 
| 269 |          if(status == VC_CONTAINER_SUCCESS) goto success; | 
| 270 |          unload_library(handle); | 
| 271 |          if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; | 
| 272 |       } | 
| 273 |    } | 
| 274 |  | 
| 275 |  error: | 
| 276 |    return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; | 
| 277 |  | 
| 278 |  success: | 
| 279 |    p_ctx->priv->module_handle = handle; | 
| 280 |    return status; | 
| 281 | } | 
| 282 |  | 
| 283 | /*****************************************************************************/ | 
| 284 | void vc_container_unload(VC_CONTAINER_T *p_ctx) | 
| 285 | { | 
| 286 |    if (p_ctx->priv->module_handle) | 
| 287 |    { | 
| 288 |       unload_library(p_ctx->priv->module_handle); | 
| 289 |       p_ctx->priv->module_handle = NULL; | 
| 290 |    } | 
| 291 | } | 
| 292 |  | 
| 293 | /****************************************************************************** | 
| 294 | Local Functions | 
| 295 | ******************************************************************************/ | 
| 296 | static void reset_context(VC_CONTAINER_T *p_ctx) | 
| 297 | { | 
| 298 |    vc_container_assert(p_ctx); | 
| 299 |     | 
| 300 |    p_ctx->capabilities = 0; | 
| 301 |    p_ctx->tracks = NULL; | 
| 302 |    p_ctx->tracks_num = 0; | 
| 303 |    p_ctx->drm = NULL; | 
| 304 |    p_ctx->priv->module = NULL; | 
| 305 |    p_ctx->priv->pf_close = NULL; | 
| 306 |    p_ctx->priv->pf_read = NULL; | 
| 307 |    p_ctx->priv->pf_write = NULL; | 
| 308 |    p_ctx->priv->pf_seek = NULL; | 
| 309 |    p_ctx->priv->pf_control = NULL; | 
| 310 |    p_ctx->priv->tmp_io = NULL; | 
| 311 | } | 
| 312 |  | 
| 313 | /*****************************************************************************/ | 
| 314 | static VC_CONTAINER_READER_OPEN_FUNC_T load_reader(void **handle, const char *name) | 
| 315 | { | 
| 316 |    return load_library(handle, name, NULL, 1); | 
| 317 | } | 
| 318 |  | 
| 319 | /*****************************************************************************/ | 
| 320 | static VC_CONTAINER_READER_OPEN_FUNC_T load_writer(void **handle, const char *name) | 
| 321 | { | 
| 322 |    return load_library(handle, name, NULL, 0); | 
| 323 | } | 
| 324 |  | 
| 325 | /*****************************************************************************/ | 
| 326 | static VC_CONTAINER_READER_OPEN_FUNC_T load_metadata_reader(void **handle, const char *name) | 
| 327 | { | 
| 328 |    #define DL_PREFIX_METADATA "metadata_" | 
| 329 |    return load_library(handle, name, DL_PREFIX_METADATA, 1); | 
| 330 | } | 
| 331 |  | 
| 332 | #if !defined(ENABLE_CONTAINERS_STANDALONE) | 
| 333 |  | 
| 334 | /*****************************************************************************/ | 
| 335 | static VC_CONTAINER_READER_OPEN_FUNC_T load_library(void **handle, const char *name, const char *ext, int read) | 
| 336 | { | 
| 337 |    #define DL_PREFIX_RD "reader_" | 
| 338 |    #define DL_PREFIX_WR "writer_" | 
| 339 |    const char *entrypt_read = {"reader_open" }; | 
| 340 |    const char *entrypt_write = {"writer_open" }; | 
| 341 |    char *dl_name, *entrypt_name; | 
| 342 |    void *dl_handle; | 
| 343 |    VC_CONTAINER_READER_OPEN_FUNC_T func = NULL; | 
| 344 |    unsigned dl_size, ep_size, name_len = strlen(name) + (ext ? strlen(ext) : 0); | 
| 345 |     | 
| 346 |    vc_container_assert(read == 0 || read == 1); | 
| 347 |     | 
| 348 |    dl_size = strlen(DL_PATH_PREFIX) + MAX(strlen(DL_PREFIX_RD), strlen(DL_PREFIX_WR)) + name_len + strlen(DL_SUFFIX) + 1; | 
| 349 |    if ((dl_name = malloc(dl_size)) == NULL) | 
| 350 |       return NULL; | 
| 351 |  | 
| 352 |    ep_size = name_len + 1 + MAX(strlen(entrypt_read), strlen(entrypt_write)) + 1; | 
| 353 |    if ((entrypt_name = malloc(ep_size)) == NULL) | 
| 354 |    { | 
| 355 |       free(dl_name); | 
| 356 |       return NULL; | 
| 357 |    } | 
| 358 |  | 
| 359 |    snprintf(dl_name, dl_size, "%s%s%s%s%s" , DL_PATH_PREFIX, read ? DL_PREFIX_RD : DL_PREFIX_WR, ext ? ext : "" , name, DL_SUFFIX); | 
| 360 |    snprintf(entrypt_name, ep_size, "%s_%s%s" , name, ext ? ext : "" , read ? entrypt_read : entrypt_write); | 
| 361 |        | 
| 362 |    if ( (dl_handle = vcos_dlopen(dl_name, VCOS_DL_NOW)) != NULL ) | 
| 363 |    { | 
| 364 |       /* Try generic entrypoint name before the mangled, full name */ | 
| 365 |       func = (VC_CONTAINER_READER_OPEN_FUNC_T)vcos_dlsym(dl_handle, read ? entrypt_read : entrypt_write); | 
| 366 | #if !defined(__VIDEOCORE__) /* The following would be pointless on MW/VideoCore */ | 
| 367 |       if (!func) func = (VC_CONTAINER_READER_OPEN_FUNC_T)vcos_dlsym(dl_handle, entrypt_name); | 
| 368 | #endif | 
| 369 |       /* Only return handle if symbol found */ | 
| 370 |       if (func) | 
| 371 |          *handle = dl_handle; | 
| 372 |       else | 
| 373 |          vcos_dlclose(dl_handle); | 
| 374 |    } | 
| 375 |    | 
| 376 |    free(entrypt_name); | 
| 377 |    free(dl_name);   | 
| 378 |    return func; | 
| 379 | } | 
| 380 |  | 
| 381 | /*****************************************************************************/ | 
| 382 | static void unload_library(void *handle) | 
| 383 | { | 
| 384 |    vcos_dlclose(handle); | 
| 385 | } | 
| 386 |  | 
| 387 | #else /* !defined(ENABLE_CONTAINERS_STANDALONE) */ | 
| 388 |  | 
| 389 | /*****************************************************************************/ | 
| 390 | static VC_CONTAINER_READER_OPEN_FUNC_T load_library(void **handle, const char *name, const char *ext, int read) | 
| 391 | { | 
| 392 |    int i; | 
| 393 |    VC_CONTAINER_PARAM_UNUSED(handle); | 
| 394 |    VC_CONTAINER_PARAM_UNUSED(ext); | 
| 395 |  | 
| 396 |    if (read) | 
| 397 |    { | 
| 398 |       for (i = 0; reader_entry_points[i].name; i++) | 
| 399 |          if (!strcasecmp(reader_entry_points[i].name, name)) | 
| 400 |             return reader_entry_points[i].func; | 
| 401 |        | 
| 402 |       for (i = 0; metadata_reader_entry_points[i].name; i++) | 
| 403 |          if (!strcasecmp(metadata_reader_entry_points[i].name, name)) | 
| 404 |             return metadata_reader_entry_points[i].func; | 
| 405 |    } | 
| 406 |    else | 
| 407 |    { | 
| 408 |       for (i = 0; writer_entry_points[i].name; i++) | 
| 409 |          if (!strcasecmp(writer_entry_points[i].name, name)) | 
| 410 |             return writer_entry_points[i].func; | 
| 411 |    } | 
| 412 |  | 
| 413 |    return NULL; | 
| 414 | } | 
| 415 |  | 
| 416 | /*****************************************************************************/ | 
| 417 | static void unload_library(void *handle) | 
| 418 | { | 
| 419 |    (void)handle; | 
| 420 | } | 
| 421 |  | 
| 422 | #endif /* !defined(ENABLE_CONTAINERS_STANDALONE) */ | 
| 423 |  | 
| 424 | /*****************************************************************************/ | 
| 425 | static const char* container_for_fileext(const char *fileext) | 
| 426 | { | 
| 427 |    int i; | 
| 428 |  | 
| 429 |    for( i = 0; fileext && extension_container_mapping[i].extension; i++ ) | 
| 430 |    { | 
| 431 |       if (!strcasecmp( fileext, extension_container_mapping[i].extension )) | 
| 432 |          return extension_container_mapping[i].container; | 
| 433 |    } | 
| 434 |  | 
| 435 |    return fileext; | 
| 436 | } | 
| 437 |  |