diff --git a/CMakeLists.txt b/CMakeLists.txt index 6345900..ee6edd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,11 +3,7 @@ cmake_minimum_required(VERSION 2.8) project(vmcs_host_apps) set(BUILD_MMAL TRUE) -if (ALL_APPS) - set(BUILD_MMAL_APPS TRUE) -else() - set(BUILD_MMAL_APPS FALSE) -endif() +set(BUILD_MMAL_APPS TRUE) set(vmcs_root ${PROJECT_SOURCE_DIR}) get_filename_component(VIDEOCORE_ROOT . ABSOLUTE) @@ -64,15 +60,13 @@ add_subdirectory(interface/khronos) if(BUILD_MMAL) include_directories(interface/mmal) add_subdirectory(interface/mmal) -endif() -if(BUILD_MMAL_APPS) add_subdirectory(containers) endif() # VidTex supports Android and Linux -if(BUILD_MMAL_COMPONENTS) +if(BUILD_MMAL_APPS) add_subdirectory(host_applications/android/apps/vidtex) -endif(BUILD_MMAL_COMPONENTS) +endif(BUILD_MMAL_APPS) add_subdirectory(middleware/openmaxil) @@ -97,9 +91,6 @@ add_subdirectory(interface/usbdk) # VMCS Host Applications #add_subdirectory(host_applications/framework) -if(BUILD_MMAL_APPS) - add_subdirectory(host_applications/vmcs) -endif() # add_subdirectory(interface/vchiq/test/win32) diff --git a/containers/CMakeLists.txt b/containers/CMakeLists.txt new file mode 100644 index 0000000..34939bb --- /dev/null +++ b/containers/CMakeLists.txt @@ -0,0 +1,190 @@ +SET( SOURCE_DIR . ) + +# We support building both static and shared libraries +if (NOT DEFINED LIBRARY_TYPE) +set(LIBRARY_TYPE SHARED) +endif (NOT DEFINED LIBRARY_TYPE) + +# Make sure the compiler can find the necessary include files +include_directories (${SOURCE_DIR}/.. ${SOURCE_DIR}/../interface/vcos) + +# Needed for the container loader +add_definitions(-DDL_PATH_PREFIX="${VMCS_PLUGIN_DIR}/") + +SET( GCC_COMPILER_FLAGS -Wall -g -O2 -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wcast-qual -Wwrite-strings -Wundef ) +SET( GCC_COMPILER_FLAGS ${GCC_COMPILER_FLAGS} -Wextra )#-Wno-missing-field-initializers ) +SET( GCC_COMPILER_FLAGS ${GCC_COMPILER_FLAGS} -D__STDC_VERSION__=199901L ) +SET( GCC_COMPILER_FLAGS ${GCC_COMPILER_FLAGS} -Wno-missing-field-initializers ) +SET( GCC_COMPILER_FLAGS ${GCC_COMPILER_FLAGS} -Wno-unused-value ) + +add_definitions( ${GCC_COMPILER_FLAGS} ) + +# Containers core library +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_io.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_io_helpers.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_codecs.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_utils.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_writer_utils.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_loader.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_filters.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_logging.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_uri.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_bits.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_list.c) +set(core_SRCS ${core_SRCS} ${SOURCE_DIR}/core/containers_index.c) + +# Containers io library +set(io_SRCS ${io_SRCS} ${SOURCE_DIR}/io/io_file.c) +set(io_SRCS ${io_SRCS} ${SOURCE_DIR}/io/io_null.c) +set(io_SRCS ${io_SRCS} ${SOURCE_DIR}/io/io_net.c) +set(io_SRCS ${io_SRCS} ${SOURCE_DIR}/io/io_pktfile.c) +set(io_SRCS ${io_SRCS} ${SOURCE_DIR}/io/io_http.c) +add_definitions( -DENABLE_CONTAINER_IO_HTTP ) + +# Containers net library +if (DEFINED MSVC) +set(net_SRCS ${net_SRCS} ${SOURCE_DIR}/net/net_sockets_common.c) +set(net_SRCS ${net_SRCS} ${SOURCE_DIR}/net/net_sockets_win32.c) +elseif (DEFINED LINUX OR DEFINED UNIX) +set(net_SRCS ${net_SRCS} ${SOURCE_DIR}/net/net_sockets_common.c) +set(net_SRCS ${net_SRCS} ${SOURCE_DIR}/net/net_sockets_bsd.c) +else (DEFINED MSVC) +set(net_SRCS ${net_SRCS} ${SOURCE_DIR}/net/net_sockets_null.c) +endif (DEFINED MSVC) +set(extra_net_SRCS net_sockets_win32.c net_sockets_win32.h net_sockets_null.c) +add_custom_target(containers_net_extra + COMMAND touch ${extra_net_SRCS} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/containers/net) + +# Packetizers library +set(packetizers_SRCS ${packetizers_SRCS} ${SOURCE_DIR}/core/packetizers.c) +set(packetizers_SRCS ${packetizers_SRCS} ${SOURCE_DIR}/mpga/mpga_packetizer.c) +set(packetizers_SRCS ${packetizers_SRCS} ${SOURCE_DIR}/mpgv/mpgv_packetizer.c) +set(packetizers_SRCS ${packetizers_SRCS} ${SOURCE_DIR}/pcm/pcm_packetizer.c) +set(packetizers_SRCS ${packetizers_SRCS} ${SOURCE_DIR}/h264/avc1_packetizer.c) + +add_library(containers ${LIBRARY_TYPE} ${core_SRCS} ${io_SRCS} ${net_SRCS} ${packetizers_SRCS}) +target_link_libraries(containers vcos) +add_dependencies(containers containers_net_extra) +install(TARGETS containers DESTINATION lib) + +set(container_readers) +set(container_writers) + +# Container modules +add_subdirectory(mp4) +set(container_readers ${container_readers} reader_mp4) +set(container_writers ${container_writers} writer_mp4) +add_subdirectory(mpeg) +set(container_readers ${container_readers} reader_ps) +add_subdirectory(mpga) +set(container_readers ${container_readers} reader_mpga) +add_subdirectory(binary) +set(container_readers ${container_readers} reader_binary) +set(container_writers ${container_writers} writer_binary) +add_subdirectory(mkv) +set(container_readers ${container_readers} reader_mkv) +add_subdirectory(wav) +set(container_readers ${container_readers} reader_wav) +add_subdirectory(asf) +set(container_readers ${container_readers} reader_asf) +set(container_writers ${container_writers} writer_asf) +add_subdirectory(flash) +set(container_readers ${container_readers} reader_flv) +add_subdirectory(avi) +set(container_readers ${container_readers} reader_avi) +set(container_writers ${container_writers} writer_avi) +add_subdirectory(rtp) +set(container_readers ${container_readers} reader_rtp) +add_subdirectory(rtsp) +set(container_readers ${container_readers} reader_rtsp) +add_subdirectory(rcv) +set(container_readers ${container_readers} reader_rcv) +add_subdirectory(rv9) +set(container_readers ${container_readers} reader_rv9) +add_subdirectory(qsynth) +set(container_readers ${container_readers} reader_qsynth) +add_subdirectory(simple) +set(container_readers ${container_readers} reader_simple) +set(container_writers ${container_writers} writer_simple) +add_subdirectory(raw) +set(container_readers ${container_readers} reader_raw_video) +set(container_writers ${container_writers} writer_raw_video) +add_subdirectory(dummy) +set(container_writers ${container_writers} writer_dummy) + +add_subdirectory(metadata/id3) +set(container_readers ${container_readers} reader_metadata_id3) + +if (${LIBRARY_TYPE} STREQUAL STATIC) +target_link_libraries(containers ${container_readers} ${container_writers}) +endif (${LIBRARY_TYPE} STREQUAL STATIC) + +# Generate test application +add_executable(containers_test test/test.c) +target_link_libraries(containers_test -Wl,--no-whole-archive containers) +install(TARGETS containers_test DESTINATION bin) + +# Generate test application +add_executable(containers_check_frame_int test/check_frame_int.c) +target_link_libraries(containers_check_frame_int -Wl,--no-whole-archive containers) +install(TARGETS containers_check_frame_int DESTINATION bin) + +# Generate autotest application +#add_executable(containers_autotest test/autotest.cpp test/crc_32.c) +#target_link_libraries(containers_autotest -Wl,--no-whole-archive containers}) +#install(TARGETS containers_autotest DESTINATION bin) + +# Helper code to provide non-blocking console input +if (WIN32) +set( NB_IO_SOURCE test/nb_io_win32.c ) +elseif (UNIX) +set( NB_IO_SOURCE test/nb_io_unix.c ) +endif (WIN32) +set(extra_test_SRCS nb_io_win32.c) +add_custom_target(containers_test_extra + COMMAND touch ${extra_test_SRCS} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/containers/test) +add_dependencies(containers containers_test_extra) + +# Generate net test applications +add_executable(containers_stream_client test/stream_client.c ${NB_IO_SOURCE}) +target_link_libraries(containers_stream_client containers) +install(TARGETS containers_stream_client DESTINATION bin) + +add_executable(containers_stream_server test/stream_server.c) +target_link_libraries(containers_stream_server containers) +install(TARGETS containers_stream_server DESTINATION bin) + +add_executable(containers_datagram_sender test/datagram_sender.c) +target_link_libraries(containers_datagram_sender containers) +install(TARGETS containers_datagram_sender DESTINATION bin) + +add_executable(containers_datagram_receiver test/datagram_receiver.c) +target_link_libraries(containers_datagram_receiver containers) +install(TARGETS containers_datagram_receiver DESTINATION bin) + +add_executable(containers_rtp_decoder test/rtp_decoder.c ${NB_IO_SOURCE}) +target_link_libraries(containers_rtp_decoder containers) +install(TARGETS containers_rtp_decoder DESTINATION bin) + +# Generate URI test application +add_executable(containers_test_uri test/test_uri.c) +target_link_libraries(containers_test_uri containers) +install(TARGETS containers_test_uri DESTINATION bin) + +# Generate URI pipe application +add_executable(containers_uri_pipe test/uri_pipe.c ${NB_IO_SOURCE}) +target_link_libraries(containers_uri_pipe containers) +install(TARGETS containers_uri_pipe DESTINATION bin) + +# Generate bit stream test application +add_executable(containers_test_bits test/test_bits.c) +target_link_libraries(containers_test_bits containers) +install(TARGETS containers_test_bits DESTINATION bin) + +# Generate packet file dump application +add_executable(containers_dump_pktfile test/dump_pktfile.c) +install(TARGETS containers_dump_pktfile DESTINATION bin) + diff --git a/containers/asf/CMakeLists.txt b/containers/asf/CMakeLists.txt new file mode 100644 index 0000000..f4e73ca --- /dev/null +++ b/containers/asf/CMakeLists.txt @@ -0,0 +1,19 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_asf ${LIBRARY_TYPE} asf_reader.c) + +target_link_libraries(reader_asf containers) + +install(TARGETS reader_asf DESTINATION ${VMCS_PLUGIN_DIR}) + +# add_library(writer_asf ${LIBRARY_TYPE} asf_writer.c) + +# target_link_libraries(writer_asf containers) + +# install(TARGETS writer_asf DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/asf/asf_reader.c b/containers/asf/asf_reader.c new file mode 100644 index 0000000..cd65516 --- /dev/null +++ b/containers/asf/asf_reader.c @@ -0,0 +1,2247 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +/* prevent double-defines when it is defined on the command line - as in the test app */ +#ifndef ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT +#endif +#ifndef ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#endif + +#define CONTAINER_HELPER_LOG_INDENT(a) (a)->priv->module->object_level + +/* For the sanity of the Visual Studio debugger make local names for structures */ +#define VC_CONTAINER_TRACK_MODULE_T ASF_VC_CONTAINER_TRACK_MODULE_T +#define VC_CONTAINER_MODULE_T ASF_VC_CONTAINER_MODULE_T +#define VC_CONTAINER_T ASF_VC_CONTAINER_T + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define ASF_TRACKS_MAX 2 +#define ASF_EXTRADATA_MAX 256 + +#define ASF_MAX_OBJECT_LEVEL 4 +#define ASF_MAX_CONSECUTIVE_UNKNOWN_OBJECTS 5 +#define ASF_MAX_OBJECT_SIZE (1<<29) /* Does not apply to the data object */ +#define ASF_OBJECT_HEADER_SIZE (16+8) +#define ASF_UNKNOWN_PTS ((uint32_t)(-1)) +#define ASF_MAX_CONSECUTIVE_CORRUPTED_PACKETS 100 +#define ASF_MAX_SEARCH_PACKETS 1000 + +#define ASF_SKIP_GUID(ctx, size, n) (size -= 16, SKIP_GUID(ctx,n)) +#define ASF_SKIP_U8(ctx, size, n) (size -= 1, SKIP_U8(ctx,n)) +#define ASF_SKIP_U16(ctx, size, n) (size -= 2, SKIP_U16(ctx,n)) +#define ASF_SKIP_U24(ctx, size, n) (size -= 3, SKIP_U24(ctx,n)) +#define ASF_SKIP_U32(ctx, size, n) (size -= 4, SKIP_U32(ctx,n)) +#define ASF_SKIP_U64(ctx, size, n) (size -= 8, SKIP_U64(ctx,n)) +#define ASF_READ_GUID(ctx, size, buffer, n) (size -= 16, READ_GUID(ctx,(uint8_t *)buffer,n)) +#define ASF_READ_U8(ctx, size, n) (size -= 1, READ_U8(ctx,n)) +#define ASF_READ_U16(ctx, size, n) (size -= 2, READ_U16(ctx,n)) +#define ASF_READ_U24(ctx, size, n) (size -= 3, READ_U24(ctx,n)) +#define ASF_READ_U32(ctx, size, n) (size -= 4, READ_U32(ctx,n)) +#define ASF_READ_U64(ctx, size, n) (size -= 8, READ_U64(ctx,n)) +#define ASF_READ_STRING(ctx, size, buffer, to_read, n) (size -= to_read, READ_STRING_UTF16(ctx,buffer,to_read,n)) +#define ASF_SKIP_STRING(ctx, size, to_read, n) (size -= to_read, SKIP_STRING_UTF16(ctx,to_read,n)) +#define ASF_READ_BYTES(ctx, size, buffer, to_read) (size -= to_read, READ_BYTES(ctx,buffer,to_read)) +#define ASF_SKIP_BYTES(ctx, size, to_read) (size -= to_read, SKIP_BYTES(ctx,to_read)) + +/* Read variable length field from p_context. */ +#define READ_VLC(p_context, length, value_if_missing, txt) \ + (length) == 1 ? READ_U8(p_context, txt) : \ + (length) == 2 ? READ_U16(p_context, txt) : \ + (length) == 3 ? READ_U32(p_context, txt) : value_if_missing + +#define CHECK_POINT(p_context, amount_to_read) do { \ + if(amount_to_read < 0) return VC_CONTAINER_ERROR_CORRUPTED; \ + if(STREAM_STATUS(p_context)) return STREAM_STATUS(p_context); } while(0) + +/****************************************************************************** +Type definitions. +******************************************************************************/ + +/** Context for our reader + */ +typedef struct +{ + uint64_t start; /* The byte offset start of the current packet in the file */ + uint32_t size; + uint32_t padding_size; + uint64_t send_time; /* read in mS, stored in uS */ + bool eos; + bool corrupted; + uint16_t bad_packets; + + /* All the different Length Types for the VLC codes */ + unsigned int replicated_data_lt; + unsigned int offset_into_media_object_lt; + unsigned int media_object_number_lt; + unsigned int payload_lt; + + unsigned int multiple_payloads; + unsigned int compressed_payloads; + + uint8_t num_payloads; + uint8_t current_payload; + uint32_t current_offset; /* The offset in the current packet for the next byte to be read */ + + /* Info already read */ + uint32_t stream_num; /* Stream number and key-frame flag */ + uint32_t media_object_num; + uint32_t media_object_off; + uint32_t payload_size; + uint32_t subpayload_size; + + /* Info read from the replicated data */ + uint32_t media_object_size; + uint64_t media_object_pts; /**< Presentation timestamp in microseconds */ + uint64_t media_object_pts_delta; /**< Presentation timestamp delta in microseconds */ + +} ASF_PACKET_STATE; + +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + /* The ID of the stream (the index in the containing array need not be the ID) */ + unsigned int stream_id; + bool b_valid; + + uint8_t extradata[ASF_EXTRADATA_MAX]; + + ASF_PACKET_STATE *p_packet_state; + ASF_PACKET_STATE local_packet_state; + + /* Simple index structure. Corresponds to the simple index in 6.1 of the spec + * This index has locations in packets, not in bytes, and relies on the + * file having fixed-length packets - as is required */ + struct + { + uint64_t offset; /**< Offset to the start of the simple index data */ + uint32_t num_entries; + int64_t time_interval; /* in uS */ + bool incomplete; /* The index does not go to the end of the file */ + } simple_index; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + int object_level; + + uint32_t packet_size; /**< Size of a data packet */ + uint64_t packets_num; /**< Number of packets contained in the data object */ + + bool broadcast; /**< Specifies if we are dealing with a broadcast stream */ + int64_t duration; /**< Duration of the stream in microseconds */ + int64_t preroll; /**< Duration of the preroll in microseconds. */ + /* This is the PTS of the first packet; all are offset by this amount. */ + uint64_t time_offset; /**< Offset added to timestamps in microseconds */ + + uint64_t data_offset; /**< Offset to the start of the data packets */ + int64_t data_size; /**< Size of the data contained in the data object */ + + /* The track objects. There's a count of these in VC_CONTAINER_T::tracks_num */ + VC_CONTAINER_TRACK_T *tracks[ASF_TRACKS_MAX]; + + /* Translation table from stream_number to index in the tracks array */ + unsigned char stream_number_to_index[128]; + + /* Data for a top-level index structure as defined in 6.2 of the spec */ + struct + { + uint64_t entry_time_interval; /* The time interval between specifiers, scaled to uS */ + uint32_t specifiers_count; /* The number of specifiers in the file, 0 if no index */ + uint64_t active_specifiers[ASF_TRACKS_MAX]; /* the specifier in use for each track, + * or >=specifiers_count if none */ + uint64_t specifiers_offset; /* The file address of the first specifier. */ + uint32_t block_count; /* The number of index blocks */ + uint64_t blocks_offset; /* The file address of the first block */ + } top_level_index; + + /* A pointer to the track (in the tracks array) which is to be used with a simple index. + * null if there is no such track */ + ASF_VC_CONTAINER_TRACK_MODULE_T *simple_index_track; + + /* Shared packet state. This is used when the tracks are in sync, + and for the track at the earliest position in the file when they are not in sync */ + ASF_PACKET_STATE packet_state; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T asf_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Prototypes for local functions +******************************************************************************/ +static VC_CONTAINER_STATUS_T asf_read_object( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_header( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_header_ext( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_file_properties( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_stream_properties( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_ext_stream_properties( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_simple_index( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_index( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_index_parameters( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_data( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_codec_list( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_content_description( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_stream_bitrate_props( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_content_encryption( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_ext_content_encryption( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_read_object_adv_content_encryption( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T asf_skip_unprocessed_object( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T seek_to_positions(VC_CONTAINER_T *p_ctx, + uint64_t track_positions[ASF_TRACKS_MAX], int64_t *p_time, + VC_CONTAINER_SEEK_FLAGS_T flags, unsigned int start_track, + bool seek_on_start_track); + +/****************************************************************************** +GUID list for the different ASF objects +******************************************************************************/ +static const GUID_T asf_guid_header = {0x75B22630, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}}; +static const GUID_T asf_guid_file_props = {0x8CABDCA1, 0xA947, 0x11CF, {0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}}; +static const GUID_T asf_guid_stream_props = {0xB7DC0791, 0xA9B7, 0x11CF, {0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}}; +static const GUID_T asf_guid_ext_stream_props = {0x14E6A5CB, 0xC672, 0x4332, {0x83, 0x99, 0xA9, 0x69, 0x52, 0x06, 0x5B, 0x5A}}; +static const GUID_T asf_guid_data = {0x75B22636, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}}; +static const GUID_T asf_guid_simple_index = {0x33000890, 0xE5B1, 0x11CF, {0x89, 0xF4, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB}}; +static const GUID_T asf_guid_index = {0xD6E229D3, 0x35DA, 0x11D1, {0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE}}; +static const GUID_T asf_guid_index_parameters = {0xD6E229DF, 0x35DA, 0x11D1, {0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE}}; +static const GUID_T asf_guid_header_ext = {0x5FBF03B5, 0xA92E, 0x11CF, {0x8E, 0xE3, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}}; +static const GUID_T asf_guid_codec_list = {0x86D15240, 0x311D, 0x11D0, {0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6}}; +static const GUID_T asf_guid_content_description = {0x75B22633, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}}; +static const GUID_T asf_guid_ext_content_description = {0xD2D0A440, 0xE307, 0x11D2, {0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50}}; +static const GUID_T asf_guid_stream_bitrate_props = {0x7BF875CE, 0x468D, 0x11D1, {0x8D, 0x82, 0x00, 0x60, 0x97, 0xC9, 0xA2, 0xB2}}; +static const GUID_T asf_guid_language_list = {0x7C4346A9, 0xEFE0, 0x4BFC, {0xB2, 0x29, 0x39, 0x3E, 0xDE, 0x41, 0x5C, 0x85}}; +static const GUID_T asf_guid_metadata = {0xC5F8CBEA, 0x5BAF, 0x4877, {0x84, 0x67, 0xAA, 0x8C, 0x44, 0xFA, 0x4C, 0xCA}}; +static const GUID_T asf_guid_padding = {0x1806D474, 0xCADF, 0x4509, {0xA4, 0xBA, 0x9A, 0xAB, 0xCB, 0x96, 0xAA, 0xE8}}; +static const GUID_T asf_guid_content_encryption = {0x2211B3FB, 0xBD23, 0x11D2, {0xB4, 0xB7, 0x00, 0xA0, 0xC9, 0x55, 0xFC, 0x6E}}; +static const GUID_T asf_guid_ext_content_encryption = {0x298AE614, 0x2622, 0x4C17, {0xB9, 0x35, 0xDA, 0xE0, 0x7E, 0xE9, 0x28, 0x9C}}; +static const GUID_T asf_guid_adv_content_encryption = {0x43058533, 0x6981, 0x49E6, {0x9B, 0x74, 0xAD, 0x12, 0xCB, 0x86, 0xD5, 0x8C}}; +static const GUID_T asf_guid_compatibility = {0x26F18B5D, 0x4584, 0x47EC, {0x9F, 0x5F, 0x0E, 0x65, 0x1F, 0x04, 0x52, 0xC9}}; +static const GUID_T asf_guid_script_command = {0x1EFB1A30, 0x0B62, 0x11D0, {0xA3, 0x9B, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6}}; +static const GUID_T asf_guid_mutual_exclusion = {0xD6E229DC, 0x35DA, 0x11D1, {0x90, 0x34, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xBE}}; + +static const GUID_T asf_guid_stream_type_video = {0xBC19EFC0, 0x5B4D, 0x11CF, {0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B}}; +static const GUID_T asf_guid_stream_type_audio = {0xF8699E40, 0x5B4D, 0x11CF, {0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B}}; + +/****************************************************************************** +List of GUIDs and their associated processing functions +******************************************************************************/ +static struct { + const GUID_T *guid; + const char *psz_name; + VC_CONTAINER_STATUS_T (*pf_func)( VC_CONTAINER_T *, int64_t ); + +} asf_object_list[] = +{ + {&asf_guid_header, "header", asf_read_object_header}, + {&asf_guid_file_props, "file properties", asf_read_object_file_properties}, + {&asf_guid_stream_props, "stream properties", asf_read_object_stream_properties}, + {&asf_guid_ext_stream_props, "extended stream properties", asf_read_object_ext_stream_properties}, + {&asf_guid_data, "data", asf_read_object_data}, + {&asf_guid_simple_index, "simple index", asf_read_object_simple_index}, + {&asf_guid_index, "index", asf_read_object_index}, + {&asf_guid_index_parameters, "index parameters", asf_read_object_index_parameters}, + {&asf_guid_header_ext, "header extension", asf_read_object_header_ext}, + {&asf_guid_codec_list, "codec list", asf_read_object_codec_list}, + {&asf_guid_content_description, "content description", asf_read_object_content_description}, + {&asf_guid_ext_content_description, "extended content description", asf_skip_unprocessed_object}, + {&asf_guid_stream_bitrate_props, "stream bitrate properties", asf_read_object_stream_bitrate_props}, + {&asf_guid_language_list, "language list", asf_skip_unprocessed_object}, + {&asf_guid_metadata, "metadata", asf_skip_unprocessed_object}, + {&asf_guid_padding, "padding", asf_skip_unprocessed_object}, + {&asf_guid_compatibility, "compatibility", asf_skip_unprocessed_object}, + {&asf_guid_script_command, "script command", asf_skip_unprocessed_object}, + {&asf_guid_mutual_exclusion, "mutual exclusion", asf_skip_unprocessed_object}, + {&asf_guid_content_encryption, "content encryption", &asf_read_object_content_encryption}, + {&asf_guid_ext_content_encryption, "extended content encryption", &asf_read_object_ext_content_encryption}, + {&asf_guid_adv_content_encryption, "advanced content encryption", &asf_read_object_adv_content_encryption}, + {0, "unknown", asf_skip_unprocessed_object} +}; + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/** Find the track associated with an ASF stream id */ +static VC_CONTAINER_TRACK_T *asf_reader_find_track( VC_CONTAINER_T *p_ctx, unsigned int stream_id, + bool b_create) +{ + VC_CONTAINER_TRACK_T *p_track = 0; + VC_CONTAINER_MODULE_T * module = p_ctx->priv->module; + unsigned int i; + + /* discard the key-frame flag */ + stream_id &= 0x7f; + + /* look to see if we have already allocated the stream */ + i = module->stream_number_to_index[stream_id]; + + if(i < p_ctx->tracks_num) /* We found it */ + p_track = p_ctx->tracks[i]; + + if(!p_track && b_create && p_ctx->tracks_num < ASF_TRACKS_MAX) + { + /* Allocate and initialise a new track */ + p_ctx->tracks[p_ctx->tracks_num] = p_track = + vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(p_track) + { + /* store the stream ID */ + p_track->priv->module->stream_id = stream_id; + + /* Store the translation table value */ + module->stream_number_to_index[stream_id] = p_ctx->tracks_num; + + /* count the track */ + p_ctx->tracks_num++; + } + } + + if(!p_track && b_create) + LOG_DEBUG(p_ctx, "could not create track for stream id: %i", stream_id); + + return p_track; +} + +/** Base function used to read an ASF object from the ASF header. + * This will read the object header do lots of sanity checking and pass on the rest + * of the reading to the object specific reading function */ +static VC_CONTAINER_STATUS_T asf_read_object( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t object_size, offset = STREAM_POSITION(p_ctx); + unsigned int i, unknown_objects = 0, is_data_object; + GUID_T guid; + + /* Sanity check the size of the data */ + if(size && size < ASF_OBJECT_HEADER_SIZE) + { + LOG_DEBUG(p_ctx, "invalid object header (too small)"); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + if(READ_GUID(p_ctx, &guid, "Object ID") != sizeof(guid)) + return STREAM_STATUS(p_ctx); + + /* Find out which GUID we are dealing with */ + for( i = 0; asf_object_list[i].guid; i++ ) + { + if(guid.word0 != asf_object_list[i].guid->word0) continue; + if(!memcmp(&guid, asf_object_list[i].guid, sizeof(guid))) break; + } + + LOG_FORMAT(p_ctx, "Object Name: %s", asf_object_list[i].psz_name); + + /* Bail out if we find too many consecutive unknown objects */ + if(!asf_object_list[i].guid) unknown_objects++; + else unknown_objects = 0; + if(unknown_objects >= ASF_MAX_CONSECUTIVE_UNKNOWN_OBJECTS) + { + LOG_DEBUG(p_ctx, "too many unknown objects"); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + is_data_object = asf_object_list[i].pf_func == asf_read_object_data; + + object_size = READ_U64(p_ctx, "Object Size"); + + /* Sanity check the object size */ + if(object_size < 0 /* Shouldn't ever get that big */ || + /* Minimum size check (data object can have a size == 0) */ + (object_size < ASF_OBJECT_HEADER_SIZE && !(is_data_object && !object_size)) || + /* Only the data object can really be massive */ + (!is_data_object && object_size > ASF_MAX_OBJECT_SIZE)) + { + LOG_DEBUG(p_ctx, "object %s has an invalid size (%"PRIi64")", + asf_object_list[i].psz_name, object_size); + return VC_CONTAINER_ERROR_CORRUPTED; + } + if(size && object_size > size) + { + LOG_DEBUG(p_ctx, "object %s is bigger than it should (%"PRIi64" > %"PRIi64")", + asf_object_list[i].psz_name, object_size, size); + return VC_CONTAINER_ERROR_CORRUPTED; + } + size = object_size; + + if(module->object_level >= 2 * ASF_MAX_OBJECT_LEVEL) + { + LOG_DEBUG(p_ctx, "object %s is too deep. skipping", asf_object_list[i].psz_name); + status = asf_skip_unprocessed_object(p_ctx, size - ASF_OBJECT_HEADER_SIZE); + /* Just bail out, hoping we have enough data */ + } + else + { + module->object_level++; + + /* Call the object specific parsing function */ + status = asf_object_list[i].pf_func(p_ctx, size - ASF_OBJECT_HEADER_SIZE); + + module->object_level--; + + if(status != VC_CONTAINER_SUCCESS) + LOG_DEBUG(p_ctx, "object %s appears to be corrupted (%i)", asf_object_list[i].psz_name, status); + } + + /* The stream position should be exactly at the end of the object */ + { + int64_t bytes_processed = STREAM_POSITION(p_ctx) - offset; + + /* fail with overruns */ + if (bytes_processed > size) + { + /* Things have gone really bad here and we ended up reading past the end of the + * object. We could maybe try to be clever and recover by seeking back to the end + * of the object. However if we get there, the file is clearly corrupted so there's + * no guarantee it would work anyway. */ + LOG_DEBUG(p_ctx, "%"PRIi64" bytes overrun past the end of object %s", + bytes_processed-size, asf_object_list[i].psz_name); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + /* Handle underruns by throwing away the data (this should never happen, but we don't really care if it does) */ + if (bytes_processed < size) + { + size -= bytes_processed; + LOG_DEBUG(p_ctx, "%"PRIi64" bytes left unread in object %s", size, asf_object_list[i].psz_name); + + if(size < ASF_MAX_OBJECT_SIZE) + SKIP_BYTES(p_ctx, size); /* read a small amount */ + else + SEEK(p_ctx, STREAM_POSITION(p_ctx) + size); /* seek a large distance */ + } + } + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF header object */ +static VC_CONTAINER_STATUS_T asf_read_object_header( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t offset = STREAM_POSITION(p_ctx); + + /* Sanity check the size of the data */ + if((size -= 6) < 0) return VC_CONTAINER_ERROR_CORRUPTED; + + SKIP_U32(p_ctx, "Number of Header Objects"); /* FIXME: could use that */ + SKIP_U8(p_ctx, "Reserved1"); + SKIP_U8(p_ctx, "Reserved2"); + + /* Read contained objects */ + module->object_level++; + while(status == VC_CONTAINER_SUCCESS && size >= ASF_OBJECT_HEADER_SIZE) + { + offset = STREAM_POSITION(p_ctx); + status = asf_read_object(p_ctx, size); + size -= (STREAM_POSITION(p_ctx) - offset); + } + module->object_level--; + + return status; +} + +/** Reads an ASF extended header object */ +static VC_CONTAINER_STATUS_T asf_read_object_header_ext( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t data_size, offset; + + ASF_SKIP_GUID(p_ctx, size, "Reserved Field 1"); + ASF_SKIP_U16(p_ctx, size, "Reserved Field 2"); + data_size = ASF_READ_U32(p_ctx, size, "Header Extension Data Size"); + + if(data_size != size) + LOG_DEBUG(p_ctx, "invalid header extension data size (%"PRIi64",%"PRIi64")", data_size, size); + + CHECK_POINT(p_ctx, size); + + /* Read contained objects */ + module->object_level++; + while(status == VC_CONTAINER_SUCCESS && size >= ASF_OBJECT_HEADER_SIZE) + { + offset = STREAM_POSITION(p_ctx); + status = asf_read_object(p_ctx, size); + size -= (STREAM_POSITION(p_ctx) - offset); + } + module->object_level--; + + return status; +} + +/** Reads an ASF file properties object */ +static VC_CONTAINER_STATUS_T asf_read_object_file_properties( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t max_packet_size; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + ASF_SKIP_GUID(p_ctx, size, "File ID"); + ASF_SKIP_U64(p_ctx, size, "File Size"); + ASF_SKIP_U64(p_ctx, size, "Creation Date"); + ASF_SKIP_U64(p_ctx, size, "Data Packets Count"); + module->duration = ASF_READ_U64(p_ctx, size, "Play Duration") / UINT64_C(10); /* read in 100nS units, stored in uS */ + ASF_SKIP_U64(p_ctx, size, "Send Duration"); + module->preroll = ASF_READ_U64(p_ctx, size, "Preroll") * UINT64_C(1000); /* read in mS, storedin uS */ + module->broadcast = ASF_READ_U32(p_ctx, size, "Flags") & 0x1; + module->packet_size = ASF_READ_U32(p_ctx, size, "Minimum Data Packet Size"); + max_packet_size = ASF_READ_U32(p_ctx, size, "Maximum Data Packet Size"); + ASF_SKIP_U32(p_ctx, size, "Maximum Bitrate"); + + if(module->preroll < module->duration) module->duration -= module->preroll; + else module->duration = 0; + + /* Sanity check the packet size */ + if(!module->packet_size) + { + LOG_DEBUG(p_ctx, "packet size cannot be 0"); + return VC_CONTAINER_ERROR_FORMAT_FEATURE_NOT_SUPPORTED; + } + + if(max_packet_size != module->packet_size) + { + LOG_DEBUG(p_ctx, "asf stream not supported (min packet size: %i != max packet size: %i)", + module->packet_size, max_packet_size); + return VC_CONTAINER_ERROR_FORMAT_FEATURE_NOT_SUPPORTED; + } + + return STREAM_STATUS(p_ctx); +} + +/** Reads the bitmapinfoheader structure contained in a stream properties object */ +static VC_CONTAINER_STATUS_T asf_read_bitmapinfoheader( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *p_track, int64_t size ) +{ + uint32_t bmih_size, formatdata_size; + uint32_t fourcc; + + /* Sanity check the size of the data */ + if(size < 40 + 11) return VC_CONTAINER_ERROR_CORRUPTED; + + /* Read the preamble to the BITMAPINFOHEADER */ + ASF_SKIP_U32(p_ctx, size, "Encoded Image Width"); + ASF_SKIP_U32(p_ctx, size, "Encoded Image Height"); + ASF_SKIP_U8(p_ctx, size, "Reserved Flags"); + formatdata_size = ASF_READ_U16(p_ctx, size, "Format Data Size"); + + /* Sanity check the size of the data */ + if(formatdata_size < 40 || size < formatdata_size) return VC_CONTAINER_ERROR_CORRUPTED; + bmih_size = ASF_READ_U32(p_ctx, size, "Format Data Size"); + if(bmih_size < 40 || bmih_size > formatdata_size) return VC_CONTAINER_ERROR_CORRUPTED; + + /* Read BITMAPINFOHEADER structure */ + p_track->format->type->video.width = ASF_READ_U32(p_ctx, size, "Image Width"); + p_track->format->type->video.height = ASF_READ_U32(p_ctx, size, "Image Height"); /* Signed */ + ASF_SKIP_U16(p_ctx, size, "Reserved"); + ASF_SKIP_U16(p_ctx, size, "Bits Per Pixel Count"); + ASF_READ_BYTES(p_ctx, size, (char *)&fourcc, 4); /* Compression ID */ + LOG_FORMAT(p_ctx, "Compression ID: %4.4s", (char *)&fourcc); + p_track->format->codec = vfw_fourcc_to_codec(fourcc); + if(p_track->format->codec == VC_CONTAINER_CODEC_UNKNOWN) + p_track->format->codec = fourcc; + ASF_SKIP_U32(p_ctx, size, "Image Size"); + ASF_SKIP_U32(p_ctx, size, "Horizontal Pixels Per Meter"); + ASF_SKIP_U32(p_ctx, size, "Vertical Pixels Per Meter"); + ASF_SKIP_U32(p_ctx, size, "Colors Used Count"); + ASF_SKIP_U32(p_ctx, size, "Important Colors Count"); + + if(!(bmih_size -= 40))return VC_CONTAINER_SUCCESS; + + if(bmih_size > ASF_EXTRADATA_MAX) + { + LOG_DEBUG(p_ctx, "extradata truncated"); + bmih_size = ASF_EXTRADATA_MAX; + } + p_track->format->extradata = p_track->priv->module->extradata; + p_track->format->extradata_size = ASF_READ_BYTES(p_ctx, size, p_track->format->extradata, bmih_size); + + return STREAM_STATUS(p_ctx); +} + +/** Reads the waveformatex structure contained in a stream properties object */ +static VC_CONTAINER_STATUS_T asf_read_waveformatex( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *p_track, int64_t size) +{ + uint16_t extradata_size; + + /* Read WAVEFORMATEX structure */ + p_track->format->codec = waveformat_to_codec(ASF_READ_U16(p_ctx, size, "Codec ID")); + p_track->format->type->audio.channels = ASF_READ_U16(p_ctx, size, "Number of Channels"); + p_track->format->type->audio.sample_rate = ASF_READ_U32(p_ctx, size, "Samples per Second"); + p_track->format->bitrate = ASF_READ_U32(p_ctx, size, "Average Number of Bytes Per Second") * 8; + p_track->format->type->audio.block_align = ASF_READ_U16(p_ctx, size, "Block Alignment"); + p_track->format->type->audio.bits_per_sample = ASF_READ_U16(p_ctx, size, "Bits Per Sample"); + extradata_size = ASF_READ_U16(p_ctx, size, "Codec Specific Data Size"); + + CHECK_POINT(p_ctx, size); + + if(!extradata_size) return VC_CONTAINER_SUCCESS; + + /* Sanity check the size of the data */ + if(extradata_size > size) return VC_CONTAINER_ERROR_CORRUPTED; + + if(extradata_size > ASF_EXTRADATA_MAX) + { + LOG_DEBUG(p_ctx, "extradata truncated"); + extradata_size = ASF_EXTRADATA_MAX; + } + p_track->format->extradata = p_track->priv->module->extradata; + p_track->format->extradata_size = ASF_READ_BYTES(p_ctx, size, p_track->format->extradata, extradata_size); + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF stream properties object */ +static VC_CONTAINER_STATUS_T asf_read_object_stream_properties( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *p_track; + unsigned int ts_length, flags; + VC_CONTAINER_ES_TYPE_T type = VC_CONTAINER_ES_TYPE_UNKNOWN; + GUID_T stream_type; + int64_t offset; + + ASF_READ_GUID(p_ctx, size, &stream_type, "Stream Type"); + ASF_SKIP_GUID(p_ctx, size, "Error Correction Type"); + + /* The time_offset field is in 100nS units. Scale back to uS */ + module->time_offset = ASF_READ_U64(p_ctx, size, "Time Offset") / UINT64_C(10); + ts_length = ASF_READ_U32(p_ctx, size, "Type-Specific Data Length"); + ASF_SKIP_U32(p_ctx, size, "Error Correction Data Length"); + flags = ASF_READ_U16(p_ctx, size, "Flags"); + ASF_SKIP_U32(p_ctx, size, "Reserved"); + + CHECK_POINT(p_ctx, size); + + /* Zero is not a valid stream id */ + if(!(flags & 0x7F)) goto skip; + + if(!memcmp(&stream_type, &asf_guid_stream_type_video, sizeof(GUID_T))) + type = VC_CONTAINER_ES_TYPE_VIDEO; + else if(!memcmp(&stream_type, &asf_guid_stream_type_audio, sizeof(GUID_T))) + type = VC_CONTAINER_ES_TYPE_AUDIO; + + /* Check we know what to do with this track */ + if(type == VC_CONTAINER_ES_TYPE_UNKNOWN) goto skip; + + /* Sanity check sizes */ + if(ts_length > size) return VC_CONTAINER_ERROR_CORRUPTED; + + p_track = asf_reader_find_track( p_ctx, flags, true); + if(!p_track) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + p_track->format->es_type = type; + + offset = STREAM_POSITION(p_ctx); + if(type == VC_CONTAINER_ES_TYPE_AUDIO) + status = asf_read_waveformatex(p_ctx, p_track, (int64_t)ts_length); + else if(type == VC_CONTAINER_ES_TYPE_VIDEO) + status = asf_read_bitmapinfoheader(p_ctx, p_track, (int64_t)ts_length); + size -= STREAM_POSITION(p_ctx) - offset; + + if(status) return status; + + p_track->priv->module->b_valid = true; + p_track->is_enabled = true; + p_track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + + /* Codec specific work-arounds */ + switch(p_track->format->codec) + { + case VC_CONTAINER_CODEC_MPGA: + /* Can't guarantee that the data is framed */ + p_track->format->flags &= ~VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + break; + default: break; + } + + skip: + if(size) SKIP_BYTES(p_ctx, size); + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF extended stream properties object */ +static VC_CONTAINER_STATUS_T asf_read_object_ext_stream_properties( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_TRACK_T *p_track; + unsigned int i, name_count, pes_count, length, stream_id; + + ASF_SKIP_U64(p_ctx, size, "Start Time"); + ASF_SKIP_U64(p_ctx, size, "End Time"); + ASF_SKIP_U32(p_ctx, size, "Data Bitrate"); + ASF_SKIP_U32(p_ctx, size, "Buffer Size"); + ASF_SKIP_U32(p_ctx, size, "Initial Buffer Fullness"); + ASF_SKIP_U32(p_ctx, size, "Alternate Data Bitrate"); + ASF_SKIP_U32(p_ctx, size, "Alternate Buffer Size"); + ASF_SKIP_U32(p_ctx, size, "Alternate Initial Buffer Fullness"); + ASF_SKIP_U32(p_ctx, size, "Maximum Object Size"); + ASF_SKIP_U32(p_ctx, size, "Flags"); + stream_id = ASF_READ_U16(p_ctx, size, "Stream Number"); + ASF_SKIP_U16(p_ctx, size, "Stream Language ID Index"); + ASF_SKIP_U64(p_ctx, size, "Average Time Per Frame"); + name_count = ASF_READ_U16(p_ctx, size, "Stream Name Count"); + pes_count = ASF_READ_U16(p_ctx, size, "Payload Extension System Count"); + + CHECK_POINT(p_ctx, size); + + p_track = asf_reader_find_track( p_ctx, stream_id, true); + if(!p_track) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + /* Stream Names */ + for(i = 0; i < name_count; i++) + { + if(size < 4) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_U16(p_ctx, size, "Language ID Index"); + length = ASF_READ_U16(p_ctx, size, "Stream Name Length"); + if(size < length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_BYTES(p_ctx, size, length); /* Stream Name */ + } + + CHECK_POINT(p_ctx, size); + + /* Payload Extension Systems */ + for(i = 0; i < pes_count; i++) + { + if(size < 22) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_GUID(p_ctx, size, "Extension System ID"); + ASF_SKIP_U16(p_ctx, size, "Extension Data Size"); + length = ASF_READ_U32(p_ctx, size, "Extension System Info Length"); + if(size < length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_BYTES(p_ctx, size, length); /* Extension System Info */ + } + + CHECK_POINT(p_ctx, size); + + /* Optional Stream Properties Object */ + if(size >= ASF_OBJECT_HEADER_SIZE) + status = asf_read_object(p_ctx, size); + + return status; +} + +/** Reads an ASF simple index object */ +static VC_CONTAINER_STATUS_T asf_read_object_simple_index( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = 0; + uint64_t time_interval, index_duration; + uint32_t count; + unsigned int i; + + ASF_SKIP_GUID(p_ctx, size, "File ID"); + + /* time in 100nS units, converted to uS */ + time_interval = ASF_READ_U64(p_ctx, size, "Index Entry Time Interval") / UINT64_C(10); + ASF_SKIP_U32(p_ctx, size, "Maximum Packet Count"); + count = ASF_READ_U32(p_ctx, size, "Index Entries Count"); + + CHECK_POINT(p_ctx, size); + + if(count > size / 6) + { + LOG_DEBUG(p_ctx, "invalid number of entries in the index (%i, %"PRIi64")", count, size / 6); + count = (uint32_t)(size / 6); + } + + /* Find the track corresponding to this index */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + if(p_ctx->tracks[i]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) continue; + if(p_ctx->tracks[i]->priv->module->simple_index.offset) continue; + break; + } + + /* Skip the index if we can't find the associated track */ + if(i == p_ctx->tracks_num || !count || !time_interval) return VC_CONTAINER_SUCCESS; + track_module = p_ctx->tracks[i]->priv->module; + + track_module->simple_index.offset = STREAM_POSITION(p_ctx); + track_module->simple_index.time_interval = time_interval; + track_module->simple_index.num_entries = count; + + /* Check that the index covers the whole duration of the stream */ + index_duration = (count * time_interval); + if(module->preroll + module->time_offset < index_duration) + index_duration -= module->preroll + module->time_offset; + else + index_duration = 0; + + if((uint64_t)module->duration > index_duration + time_interval) + { + track_module->simple_index.incomplete = true; + } + + LOG_DEBUG(p_ctx, "index covers %fS on %fS", + (float)index_duration / 1E6, (float)module->duration / 1E6); + +#if defined(ENABLE_CONTAINERS_LOG_FORMAT) && defined(ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE) + for(i = 0; i < count; i++) + { + LOG_FORMAT(p_ctx, "Entry: %u", i); + ASF_SKIP_U32(p_ctx, size, "Packet Number"); + ASF_SKIP_U16(p_ctx, size, "Packet Count"); + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) break; + } + size = i * 6; +#else + size = CACHE_BYTES(p_ctx, count * 6); +#endif + + /* Check that the index is complete */ + if(size / 6 != count ) + { + LOG_DEBUG(p_ctx, "index is incomplete (%i entries on %i)", (int)size / 6, count); + track_module->simple_index.num_entries = (uint32_t)(size / 6); + track_module->simple_index.incomplete = true; + } + + /* If we haven't had an index before, or this track is enabled, we'll store this one. + * (Usually there will only be one video track, and it will be enabled, so both tests + * will pass. This check is an attempt to handle content not structured as it should be) */ + if ((!module->simple_index_track) || (p_ctx->tracks[i]->is_enabled)) + { + /* Save the track so we don't have to look for it in when seeking */ + module->simple_index_track = track_module; + } + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF index object */ +static VC_CONTAINER_STATUS_T asf_read_object_index( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t i, specifiers_count, blocks_count; + uint32_t best_specifier_type[ASF_TRACKS_MAX] = {0}; + + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + /* Read the time interval and scale to microseconds */ + module->top_level_index.entry_time_interval + = (uint64_t)ASF_READ_U32(p_ctx, size, "Index Entry Time Interval") * INT64_C(1000); + + module->top_level_index.specifiers_count + = specifiers_count + = (uint32_t)ASF_READ_U16(p_ctx, size, "Index Specifiers Count"); + + module->top_level_index.block_count + = blocks_count + = ASF_READ_U32(p_ctx, size, "Index Blocks Count"); + + CHECK_POINT(p_ctx, size); + + /* Index specifiers. Search for the one we like best */ + if(size < specifiers_count * 4) return VC_CONTAINER_ERROR_CORRUPTED; + for(i = 0; i < specifiers_count; i++) + { + uint32_t stream_id = (uint32_t)ASF_READ_U16(p_ctx, size, "Stream Number"); + uint32_t index_type = (uint32_t)ASF_READ_U16(p_ctx, size, "Index Type"); + + /* Find the track index for this stream */ + unsigned track = module->stream_number_to_index[stream_id]; + + if ((track < ASF_TRACKS_MAX) && + (index_type > best_specifier_type[track])) + { + /* We like this better than any we have seen before. Note - if we don't like any + * the file must be subtly corrupt - best we say nothing, and attempt a seek with + * the data for the first specifier, it will be better than nothing. At worst it + * will play until a seek is attempted */ + module->top_level_index.active_specifiers[track] = i; + best_specifier_type[track] = index_type; + } + } + + for (i = 0; i < p_ctx->tracks_num; i++) + { + LOG_DEBUG(p_ctx, "indexing track %"PRIu32" with specifier %"PRIu32, + i, module->top_level_index.active_specifiers[i]); + } + + CHECK_POINT(p_ctx, size); + + /* The blocks start here */ + module->top_level_index.blocks_offset = STREAM_POSITION(p_ctx); + + /* Index blocks */ +#if !(defined(ENABLE_CONTAINERS_LOG_FORMAT) && defined(ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE)) + blocks_count = 0; /* Don't log the index. Note we'll get a warning on unprocessed data */ +#endif + /* coverity[dead_error_condition] Code needs to stay there for debugging purposes */ + for(i = 0; i < blocks_count; i++) + { + uint32_t j, k, count = ASF_READ_U32(p_ctx, size, "Index Entry Count"); + + for(j = 0; j < specifiers_count; j++) + { + ASF_SKIP_U64(p_ctx, size, "Block Positions"); + } + for(j = 0; j < count; j++) + { + for(k = 0; k < specifiers_count; k++) + { + ASF_SKIP_U32(p_ctx, size, "Offsets"); + } + } + } + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF index parameters object */ +static VC_CONTAINER_STATUS_T asf_read_object_index_parameters( VC_CONTAINER_T *p_ctx, int64_t size ) +{ +#ifdef ENABLE_CONTAINERS_LOG_FORMAT + /* This is added for debugging only. The spec (section 4.9) states that this is also enclosed in + * the index object (see above) and that they are identical. I think they aren't always... */ + uint32_t i, specifiers_count; + + /* Read the time interval in milliseconds */ + ASF_SKIP_U32(p_ctx, size, "Index Entry Time Interval"); + + specifiers_count = (uint32_t)ASF_READ_U16(p_ctx, size, "Index Specifiers Count"); + + CHECK_POINT(p_ctx, size); + + /* Index specifiers. Search for the one we like best */ + if(size < specifiers_count * 4) return VC_CONTAINER_ERROR_CORRUPTED; + for(i = 0; i < specifiers_count; i++) + { + ASF_SKIP_U16(p_ctx, size, "Stream Number"); + ASF_SKIP_U16(p_ctx, size, "Index Type"); + } +#endif + CHECK_POINT(p_ctx, size); + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF codec list object */ +static VC_CONTAINER_STATUS_T asf_read_object_codec_list( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t i, count, length; + + ASF_SKIP_GUID(p_ctx, size, "Reserved"); + count = ASF_READ_U32(p_ctx, size, "Codec Entries Count"); + + CHECK_POINT(p_ctx, size); + + /* Codec entries */ + for(i = 0; i < count; i++) + { + ASF_SKIP_U16(p_ctx, size, "Type"); + length = ASF_READ_U16(p_ctx, size, "Codec Name Length"); + if(size < length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_STRING(p_ctx, size, length * 2, "Codec Name"); + length = ASF_READ_U16(p_ctx, size, "Codec Description Length"); + if(size < length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_STRING(p_ctx, size, length * 2, "Codec Description"); + length = ASF_READ_U16(p_ctx, size, "Codec Information Length"); + if(size < length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_BYTES(p_ctx, size, length); + + CHECK_POINT(p_ctx, size); + } + + return status; +} + +/** Reads an ASF content description object */ +static VC_CONTAINER_STATUS_T asf_read_object_content_description( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint16_t t_length, a_length, c_length, d_length, r_length; + + t_length = ASF_READ_U16(p_ctx, size, "Title Length"); + a_length = ASF_READ_U16(p_ctx, size, "Author Length"); + c_length = ASF_READ_U16(p_ctx, size, "Copyright Length"); + d_length = ASF_READ_U16(p_ctx, size, "Description Length"); + r_length = ASF_READ_U16(p_ctx, size, "Rating Length"); + + CHECK_POINT(p_ctx, size); + + if(size < t_length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_STRING(p_ctx, size, t_length, "Title"); + if(size < a_length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_STRING(p_ctx, size, a_length, "Author"); + if(size < c_length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_STRING(p_ctx, size, c_length, "Copyright"); + if(size < d_length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_STRING(p_ctx, size, d_length, "Description"); + if(size < r_length) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_STRING(p_ctx, size, r_length, "Rating"); + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF stream bitrate properties object */ +static VC_CONTAINER_STATUS_T asf_read_object_stream_bitrate_props( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint16_t i, count; + + count = ASF_READ_U16(p_ctx, size, "Bitrate Records Count"); + + /* Bitrate records */ + if(size < count * 6) return VC_CONTAINER_ERROR_CORRUPTED; + for(i = 0; i < count; i++) + { + ASF_SKIP_U16(p_ctx, size, "Flags"); + ASF_SKIP_U32(p_ctx, size, "Average Bitrate"); + } + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF content encryption object */ +static VC_CONTAINER_STATUS_T asf_read_object_content_encryption( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t length; + + length = ASF_READ_U32(p_ctx, size, "Secret Data Length"); + ASF_SKIP_BYTES(p_ctx, size, length); + + length = ASF_READ_U32(p_ctx, size, "Protection Type Length"); + ASF_SKIP_BYTES(p_ctx, size, length); + + length = ASF_READ_U32(p_ctx, size, "Key ID Length"); + ASF_SKIP_BYTES(p_ctx, size, length); + + length = ASF_READ_U32(p_ctx, size, "License URL Length"); + ASF_SKIP_BYTES(p_ctx, size, length); /* null-terminated ASCII string */ + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF extended content encryption object */ +static VC_CONTAINER_STATUS_T asf_read_object_ext_content_encryption( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t length; + + length = ASF_READ_U32(p_ctx, size, "Data Size"); + ASF_SKIP_BYTES(p_ctx, size, length); + + return STREAM_STATUS(p_ctx); +} + +/** Reads an ASF advanced content encryption object */ +static VC_CONTAINER_STATUS_T asf_read_object_adv_content_encryption( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t i, count; + + count = ASF_READ_U16(p_ctx, size, "Content Encryption Records Count"); + + for(i = 0; i < count; i++) + { + uint32_t j, rec_count, data_size, length; + + ASF_SKIP_GUID(p_ctx, size, "System ID"); + ASF_SKIP_U32(p_ctx, size, "System Version"); + rec_count = ASF_READ_U16(p_ctx, size, "Encrypted Object Record Count"); + + CHECK_POINT(p_ctx, size); + + for(j = 0; j < rec_count; j++) + { + ASF_SKIP_U16(p_ctx, size, "Encrypted Object ID Type"); + length = ASF_READ_U16(p_ctx, size, "Encrypted Object ID Length"); + if(length > size) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_BYTES(p_ctx, size, length); + CHECK_POINT(p_ctx, size); + } + + data_size = ASF_READ_U32(p_ctx, size, "Data Size"); + if(data_size > size) return VC_CONTAINER_ERROR_CORRUPTED; + ASF_SKIP_BYTES(p_ctx, size, data_size); + CHECK_POINT(p_ctx, size); + } + + return STREAM_STATUS(p_ctx); +} + +/** Skips over an object that is if a type we don't handle, or is nested too deep */ +static VC_CONTAINER_STATUS_T asf_skip_unprocessed_object( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + LOG_DEBUG(p_ctx, "%"PRIi64" bytes ignored in unhandled object", size); + + if(size < ASF_MAX_OBJECT_SIZE) + SKIP_BYTES(p_ctx, size); /* read a small amount */ + else + SEEK(p_ctx, STREAM_POSITION(p_ctx) + size); /* seek a large distance */ + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_find_packet_header( VC_CONTAINER_T *p_ctx, + ASF_PACKET_STATE *p_state ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int search_size = 64*1024; /* should be max packet size according to spec */ +#ifdef ENABLE_CONTAINER_LOG_DEBUG + uint64_t offset = STREAM_POSITION(p_ctx); +#endif + uint8_t h[3]; + VC_CONTAINER_PARAM_UNUSED(p_state); + + /* Limit the search up to what should theoretically be the packet boundary */ + if(module->packet_size) + search_size = module->packet_size - + (STREAM_POSITION(p_ctx) - module->data_offset) % module->packet_size; + + for(; search_size > sizeof(h); search_size--) + { + if(PEEK_BYTES(p_ctx, h, sizeof(h)) != sizeof(h)) + return STREAM_STATUS(p_ctx); + + if(!h[0] && !h[1] && h[2] == 0x82) + { + search_size = 2; + break; /* Got it */ + } + + SKIP_BYTES(p_ctx, 1); + } + + /* If we failed, we just skip to the theoretical packet boundary */ + SKIP_BYTES(p_ctx, search_size); + + LOG_DEBUG(p_ctx, "found potential sync, discarded %"PRIu64" bytes)", + STREAM_POSITION(p_ctx) - offset); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_read_packet_header( VC_CONTAINER_T *p_ctx, + ASF_PACKET_STATE *p_state, uint64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint64_t offset = STREAM_POSITION(p_ctx); + uint8_t flags, property_flags, length; + VC_CONTAINER_PARAM_UNUSED(size); + + p_state->start = offset; + + LOG_FORMAT(p_ctx, "Packet Offset: %"PRIu64, offset); + + if ((module->data_size > 0) && (offset >= (module->data_size + module->data_offset))) + { + return VC_CONTAINER_ERROR_EOS; + } + + /* Find out whether we are dealing with error correction data or payload parsing info */ + if( PEEK_U8(p_ctx) >> 7 ) + { + /* We have error correction data */ + flags = READ_U8(p_ctx, "Error Correction Flags"); + length = flags & 0xF; + SKIP_BYTES(p_ctx, length); /* Error correction data */ + } + + /* Payload parsing information */ + flags = READ_U8(p_ctx, "Length Type Flags"); + p_state->multiple_payloads = flags & 1; + property_flags = READ_U8(p_ctx, "Property Flags"); + p_state->replicated_data_lt = (property_flags >> 0) & 0x3; + p_state->offset_into_media_object_lt = (property_flags >> 2) & 0x3; + p_state->media_object_number_lt = (property_flags >> 4) & 0x3; + + /* Sanity check stream number length type */ + if(((property_flags >> 6) & 0x3) != 1) + goto error; + + /* If there's no packet size field we default to the size in the file header. */ + p_state->size = READ_VLC(p_ctx, (flags >> 5) & 0x3 /* Packet length type */, + module->packet_size, "Packet Length"); + + READ_VLC(p_ctx, (flags>>1)&0x3 /* Sequence type */, 0, "Sequence"); + p_state->padding_size = READ_VLC(p_ctx, (flags>>3)&0x3 /* Padding length type */, 0, "Padding Length"); + p_state->send_time = READ_U32(p_ctx, "Send Time") * UINT64_C(1000); /* Read in millisecond units, stored in uS */ + SKIP_U16(p_ctx, "Duration"); /* in milliseconds */ + + p_state->num_payloads = 1; + p_state->current_payload = 0; + if(p_state->multiple_payloads) + { + LOG_FORMAT(p_ctx, "Multiple Payloads"); + flags = READ_U8(p_ctx, "Payload Flags"); + p_state->num_payloads = flags & 0x3F; + LOG_FORMAT(p_ctx, "Number of Payloads: %i", p_state->num_payloads); + p_state->payload_lt = (flags >> 6) & 3; + + /* Sanity check */ + if(!p_state->num_payloads) goto error; + } + + /* Update the current offset in the packet. */ + p_state->current_offset = STREAM_POSITION(p_ctx) - offset; + + /* Sanity check offset */ + if(p_state->current_offset > p_state->size) goto error; + + /* Sanity check padding size */ + if(p_state->padding_size + p_state->current_offset > p_state->size) goto error; + + /* Sanity check packet size */ + if(!module->broadcast && + (p_state->size != module->packet_size)) goto error; + + return STREAM_STATUS(p_ctx); + + error: + LOG_FORMAT(p_ctx, "Invalid payload parsing information (offset %"PRIu64")", STREAM_POSITION(p_ctx)); + return STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS ? + VC_CONTAINER_ERROR_CORRUPTED : STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_read_payload_header( VC_CONTAINER_T *p_ctx, + ASF_PACKET_STATE *p_state /* uint64_t size */ ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t rep_data_length; + + if(p_state->current_payload >= p_state->num_payloads) + return VC_CONTAINER_ERROR_CORRUPTED; + + p_state->stream_num = READ_U8(p_ctx, "Stream Number"); + if(!(p_state->stream_num & 0x7F)) return VC_CONTAINER_ERROR_CORRUPTED; + + p_state->media_object_num = READ_VLC(p_ctx, p_state->media_object_number_lt, 0, "Media Object Number"); + + /* For a compressed packet this field is a timestamp, and is moved to p_state->media_object_pts later */ + p_state->media_object_off = READ_VLC(p_ctx, p_state->offset_into_media_object_lt, 0, "Offset Into Media Object"); + rep_data_length = READ_VLC(p_ctx, p_state->replicated_data_lt, 0, "Replicated Data Length"); + + /* Sanity check the replicated data length */ + if(rep_data_length && rep_data_length != 1 && + (rep_data_length < 8 || + STREAM_POSITION(p_ctx) - p_state->start + p_state->padding_size + rep_data_length > p_state->size)) + { + LOG_FORMAT(p_ctx, "invalid replicated data length"); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + /* Read what we need from the replicated data */ + if(rep_data_length > 1) + { + p_state->media_object_size = READ_U32(p_ctx, "Media Object Size"); + p_state->media_object_pts = READ_U32(p_ctx, "Presentation Time") * UINT64_C(1000); + p_state->compressed_payloads = 0; + SKIP_BYTES(p_ctx, rep_data_length - 8); /* Rest of replicated data */ + } + else if(rep_data_length == 1) + { + LOG_FORMAT(p_ctx, "Compressed Payload Data"); + p_state->media_object_pts_delta = READ_U8(p_ctx, "Presentation Time Delta") * UINT64_C(1000); + p_state->compressed_payloads = 1; + + /* Move the pts from media_object_off where it was read, and adjust it */ + p_state->media_object_off *= UINT64_C(1000); + p_state->media_object_pts = p_state->media_object_off - p_state->media_object_pts_delta; + p_state->media_object_off = 0; + p_state->media_object_size = 0; + } + else + { + p_state->media_object_size = 0; + p_state->media_object_pts = p_state->send_time; + p_state->compressed_payloads = 0; + } + + if(p_state->media_object_pts > module->preroll + module->time_offset) + p_state->media_object_pts -= (module->preroll + module->time_offset); + else p_state->media_object_pts = 0; + + p_state->payload_size = p_state->size - p_state->padding_size - (STREAM_POSITION(p_ctx) - p_state->start); + if(p_state->multiple_payloads) + { + p_state->payload_size = READ_VLC(p_ctx, p_state->payload_lt, 0, "Payload Length"); + if(!p_state->payload_size) return VC_CONTAINER_ERROR_CORRUPTED; + } + else + LOG_FORMAT(p_ctx, "Payload Length: %i", p_state->payload_size); + + if(p_state->payload_size >= p_state->size) return VC_CONTAINER_ERROR_CORRUPTED; + + p_state->subpayload_size = p_state->payload_size; + + /* Update current_offset to reflect the variable number of bytes we just read */ + p_state->current_offset = STREAM_POSITION(p_ctx) - p_state->start; + + /* Sanity check offset */ + if(p_state->current_offset > p_state->size) return VC_CONTAINER_ERROR_CORRUPTED; + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_read_object_data( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + VC_CONTAINER_PARAM_UNUSED(size); + + SKIP_GUID(p_ctx, "File ID"); + SKIP_U64(p_ctx, "Total Data Packets"); + SKIP_U16(p_ctx, "Reserved"); + module->data_offset = STREAM_POSITION(p_ctx); + + /* Initialise state for all tracks */ + module->packet_state.start = module->data_offset; + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *p_track = p_ctx->tracks[i]; + p_track->priv->module->p_packet_state = &module->packet_state; + } + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +/* Read the next sub-payload or next payload */ +static VC_CONTAINER_STATUS_T asf_read_next_payload_header( VC_CONTAINER_T *p_ctx, + ASF_PACKET_STATE *p_state, uint32_t *pi_track, uint32_t *pi_length) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + + if(p_state->subpayload_size) + { + /* We still haven't read the current subpayload, return the info we already have */ + goto end; + } + + /* Check if we're done reading a packet */ + if(p_state->current_payload >= p_state->num_payloads) + { + /* Skip the padding at the end */ + if(p_state->size) + { + int32_t pad_length = p_state->size - (STREAM_POSITION(p_ctx) - p_state->start); + if(pad_length < 0) return VC_CONTAINER_ERROR_CORRUPTED; + SKIP_BYTES(p_ctx, pad_length); /* Padding */ + } + + /* Read the header for the next packet */ + module->object_level = 0; /* For debugging */ + status = asf_read_packet_header( p_ctx, p_state, (uint64_t)0/*size???*/ ); + module->object_level = 1; /* For debugging */ + if(status != VC_CONTAINER_SUCCESS) return status; + } + + /* Check if we're done reading a payload */ + if(!p_state->payload_size) + { + /* Read the payload header */ + status = asf_read_payload_header( p_ctx, p_state ); + if(status != VC_CONTAINER_SUCCESS) return status; + } + + /* For compressed payloads, payload_size != subpayload_size */ + if(p_state->compressed_payloads && p_state->payload_size) + { + p_state->payload_size--; + p_state->subpayload_size = READ_U8(p_ctx, "Sub-Payload Data Length"); + if(p_state->subpayload_size > p_state->payload_size) + { + /* TODO: do something ? */ + LOG_DEBUG(p_ctx, "subpayload is too big"); + p_state->subpayload_size = p_state->payload_size; + } + p_state->media_object_off = 0; + p_state->media_object_size = p_state->subpayload_size; + p_state->media_object_pts += p_state->media_object_pts_delta; + } + + end: + /* We've read the payload header, return the requested info */ + if(pi_track) *pi_track = module->stream_number_to_index[p_state->stream_num & 0x7F]; + if(pi_length) *pi_length = p_state->subpayload_size; + + p_state->current_offset = STREAM_POSITION(p_ctx) - p_state->start; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +/* read the next payload (not sub-payload) */ +static VC_CONTAINER_STATUS_T asf_read_next_payload( VC_CONTAINER_T *p_ctx, + ASF_PACKET_STATE *p_state, uint8_t *p_data, uint32_t *pi_size ) +{ + uint32_t subpayload_size = p_state->subpayload_size; + + if(p_data && *pi_size < subpayload_size) subpayload_size = *pi_size; + + if(!p_state->subpayload_size) + return VC_CONTAINER_SUCCESS; + + p_state->payload_size -= subpayload_size; + if(!p_state->payload_size) p_state->current_payload++; + p_state->subpayload_size -= subpayload_size; + p_state->media_object_off += subpayload_size; + + if(p_data) *pi_size = READ_BYTES(p_ctx, p_data, subpayload_size); + else *pi_size = SKIP_BYTES(p_ctx, subpayload_size); + + p_state->current_offset += subpayload_size; + + if(*pi_size!= subpayload_size) + return STREAM_STATUS(p_ctx); + + return VC_CONTAINER_SUCCESS; +} + +/****************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +/*****************************************************************************/ +/** Read data from the ASF file + +@param p_ctx Context for the file being read from + +@param p_packet Packet information. Includes data buffer and stream ID as aprropriate. + +@param flags Flags controlling the read. + May request reading only, skipping a packet or force access to a set track. + +******************************************************************************/ +static VC_CONTAINER_STATUS_T asf_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *global_module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + ASF_PACKET_STATE *p_state; + uint32_t buffer_size = 0, track, data_size; + uint64_t state_pos; + + LOG_DEBUG(p_ctx, "asf_reader_read track %"PRIu32" flags %u", p_packet->track, flags); + + /* If a specific track has been selected, we need to use the track packet state */ + if(flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) + { + vc_container_assert(p_packet->track < p_ctx->tracks_num); + /* The state to use is the one referred to by the track we selected */ + p_state = p_ctx->tracks[p_packet->track]->priv->module->p_packet_state; + } + else + { + /* No specific track was selected. Read the next data from the global position. */ + p_state = &global_module->packet_state; + } + + /* Stop if the stream can't be read */ + if(p_state->eos) return VC_CONTAINER_ERROR_EOS; + if(p_state->corrupted) return VC_CONTAINER_ERROR_CORRUPTED; + + /* If we aren't in the right position in the file go there now. */ + state_pos = p_state->start + p_state->current_offset; + if ((uint64_t)STREAM_POSITION(p_ctx) != state_pos) + { + LOG_DEBUG(p_ctx, "seeking from %"PRIu64" to %"PRIu64, STREAM_POSITION(p_ctx), state_pos); + SEEK(p_ctx, state_pos); + } + + /* Look at the next payload header */ + status = asf_read_next_payload_header( p_ctx, p_state, &track, &data_size ); + if((status == VC_CONTAINER_ERROR_CORRUPTED) + && (p_state->bad_packets < ASF_MAX_CONSECUTIVE_CORRUPTED_PACKETS)) + { + /* If the current packet is corrupted we will try to search for the next packet */ + uint32_t corrupted = p_state->bad_packets; + LOG_DEBUG(p_ctx, "packet offset %"PRIi64" is corrupted", p_state->start); + memset(p_state, 0, sizeof(*p_state)); + p_state->bad_packets = corrupted + 1; + + /* TODO: flag discontinuity */ + + if(asf_find_packet_header(p_ctx, p_state) == VC_CONTAINER_SUCCESS) + { + p_state->start = STREAM_POSITION(p_ctx); + return VC_CONTAINER_ERROR_CONTINUE; + } + } + if(status == VC_CONTAINER_ERROR_EOS) p_state->eos = true; + if(status == VC_CONTAINER_ERROR_CORRUPTED) p_state->corrupted = true; + if(status != VC_CONTAINER_SUCCESS) + { + return status; + } + + p_state->bad_packets = 0; + + /* bad track number or track is disabled */ + if(track >= p_ctx->tracks_num || !p_ctx->tracks[track]->is_enabled) + { + LOG_DEBUG(p_ctx, "skipping packet because track %u is invalid or disabled", track); + + /* Skip payload by reading with a null buffer */ + status = asf_read_next_payload(p_ctx, p_state, 0, &data_size ); + if(status != VC_CONTAINER_SUCCESS) return status; + return VC_CONTAINER_ERROR_CONTINUE; + } + + track_module = p_ctx->tracks[track]->priv->module; + + /* If we are reading from the global state, and the track we found is not on the global state, + * either skip the data or reconnect it to the global state */ + if ((p_state == &global_module->packet_state) && + (track_module->p_packet_state != &global_module->packet_state)) + { + uint64_t track_pos = + track_module->p_packet_state->start + + track_module->p_packet_state->current_offset; + + /* Check if the end of the current packet is beyond the track's position */ + if (track_pos > state_pos + track_module->p_packet_state->size) + { + LOG_DEBUG(p_ctx, "skipping packet from track %u as it has already been read", track); + status = asf_read_next_payload(p_ctx, p_state, 0, &data_size); + + if(status != VC_CONTAINER_SUCCESS) return status; + return VC_CONTAINER_ERROR_CONTINUE; + } + else + { + LOG_DEBUG(p_ctx, "switching track index %u location %"PRIu64" back to global state", track, track_pos); + track_module->p_packet_state = &global_module->packet_state; + + /* Update the global state to the precise position */ + global_module->packet_state = track_module->local_packet_state; + return VC_CONTAINER_ERROR_CONTINUE; + } + } + + /* If we are forcing, and the data is from a different track, skip it. + * We may need to move the track we want onto a local state. */ + if ((flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) + && (track != p_packet->track)) + { + track_module = p_ctx->tracks[p_packet->track]->priv->module; + + /* If the track we found is on the same state as the track we want they must both be on the global state */ + if (p_ctx->tracks[track]->priv->module->p_packet_state == p_state) + { + LOG_DEBUG(p_ctx, "switching track index %u location %"PRIu64" away from global state", p_packet->track, state_pos); + + /* Change the track we want onto a local state */ + track_module->p_packet_state = &track_module->local_packet_state; + + /* Copy the global state into the local state for the track we are forcing */ + track_module->local_packet_state = global_module->packet_state; + } + + LOG_DEBUG(p_ctx, "skipping packet from track %u while forcing %u", track, p_packet->track); + status = asf_read_next_payload(p_ctx, track_module->p_packet_state, 0, &data_size ); + return VC_CONTAINER_ERROR_CONTINUE; + } + + /* If we arrive here either the data is from the track we are forcing, or we are not forcing + * and we haven't already read the data while forcing that track */ + + /* If skip, and no info required, skip over it and return now. */ + if((flags & VC_CONTAINER_READ_FLAG_SKIP) && !(flags & VC_CONTAINER_READ_FLAG_INFO)) + return asf_read_next_payload(p_ctx, p_state, 0, &data_size ); + + /* Fill-in the packet information */ + if(p_state->media_object_pts == ASF_UNKNOWN_PTS || p_state->media_object_off) + p_packet->dts = p_packet->pts = VC_CONTAINER_TIME_UNKNOWN; + else + p_packet->dts = p_packet->pts = p_state->media_object_pts; + + p_packet->flags = 0; + + if(p_state->stream_num >> 7) p_packet->flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME; + + if(!p_state->media_object_off) p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + + if(p_state->media_object_size && + p_state->media_object_off + data_size >= p_state->media_object_size) + p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + + if(!p_state->media_object_size) + p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + + p_packet->track = track; + + p_packet->frame_size = p_state->media_object_size; + + p_packet->size = data_size; + + /* If the skip flag is set (Info must have been too) skip the data and return */ + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + { + /* Skip payload by reading with a null buffer */ + return asf_read_next_payload(p_ctx, p_state, 0, &data_size ); + } + else if(flags & VC_CONTAINER_READ_FLAG_INFO) + { + return VC_CONTAINER_SUCCESS; + } + + /* Read the payload data */ + buffer_size = p_packet->buffer_size; + status = asf_read_next_payload(p_ctx, p_state, p_packet->data, &buffer_size ); + if(status != VC_CONTAINER_SUCCESS) + { + /* FIXME */ + return status; + } + + p_packet->size = buffer_size; + if(buffer_size != data_size) + p_packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + LOG_DEBUG(p_ctx, "asf_reader_read exit %u PTS %"PRIi64" track %"PRIu32, status, p_packet->pts, track); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_reader_index_find_time( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T* track_module, int64_t time, uint32_t *packet_num, bool forward ) +{ + VC_CONTAINER_STATUS_T status; + uint32_t entry, previous_packet_num; + bool eos = false; + + /* Default to beginning of file in case of error */ + *packet_num = 0; + + /* Special case - time zero is beginning of file */ + if(time == 0) {return VC_CONTAINER_SUCCESS;} + + /* Sanity checking */ + if(!track_module->simple_index.num_entries) return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + if(!track_module->simple_index.time_interval) return VC_CONTAINER_ERROR_CORRUPTED; + + entry = time / track_module->simple_index.time_interval; + LOG_DEBUG(p_ctx, "entry: %i, offset: %"PRIi64", interv: %"PRIi64, entry, + track_module->simple_index.offset, track_module->simple_index.time_interval); + if(entry >= track_module->simple_index.num_entries) + { + entry = track_module->simple_index.num_entries - 1; + eos = true; + } + + /* Fetch the entry from the index */ + status = SEEK(p_ctx, track_module->simple_index.offset + 6 * entry); + if(status != VC_CONTAINER_SUCCESS) return status; + *packet_num = READ_U32(p_ctx, "Packet Number"); + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) return STREAM_STATUS(p_ctx); + + /* When asking for the following keyframe we need to find the next entry with a greater + * packet number */ + previous_packet_num = *packet_num; + while(!eos && forward && previous_packet_num == *packet_num) + { + if(++entry == track_module->simple_index.num_entries) {eos = true; break;} + status = SEEK(p_ctx, track_module->simple_index.offset + 6 * entry); + if(status != VC_CONTAINER_SUCCESS) break; + *packet_num = READ_U32(p_ctx, "Packet Number"); + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) break; + } + + if(eos && track_module->simple_index.incomplete) return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + else if(eos) return VC_CONTAINER_ERROR_EOS; + else return STREAM_STATUS(p_ctx); +} + +#if 0 +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_reader_index_find_packet( VC_CONTAINER_T *p_ctx, + unsigned int track, uint32_t *packet_num, bool forward ) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = 0; + uint32_t i, prev_packet_num = 0, next_packet_num; + bool eos = false; + + /* Sanity checking */ + if(track >= p_ctx->tracks_num) return VC_CONTAINER_ERROR_FAILED; + track_module = p_ctx->tracks[track]->priv->module; + if(!track_module->num_index_entries) return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + if(!track_module->index_time_interval) return VC_CONTAINER_ERROR_CORRUPTED; + + status = SEEK(p_ctx, track_module->index_offset); + if(status != VC_CONTAINER_SUCCESS) return status; + + /* Loop through all the entries in the index */ + for(i = 0; i < track_module->num_index_entries; i++) + { + next_packet_num = READ_U32(p_ctx, "Packet Number"); + SKIP_U16(p_ctx, "Packet Count"); + if(next_packet_num > *packet_num) break; + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) break; + prev_packet_num = next_packet_num; + } + if(i == track_module->num_index_entries ) eos = true; + + if(STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS && !eos) + { + if(forward) *packet_num = next_packet_num; + else *packet_num = prev_packet_num; + } + + if(eos && track_module->index_incomplete) return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + else if(eos) return VC_CONTAINER_ERROR_EOS; + else return STREAM_STATUS(p_ctx); +} +#endif + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_reader_find_next_frame( VC_CONTAINER_T *p_ctx, + unsigned int track, ASF_PACKET_STATE *p_state, bool keyframe, bool timeout ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t data_track, data_size; + unsigned int packets = 0; + + if(p_ctx->tracks[track]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) + keyframe = false; + + /* We still need to go to the right payload */ + while(status == VC_CONTAINER_SUCCESS && + (!timeout || packets++ < ASF_MAX_SEARCH_PACKETS)) + { + status = asf_read_next_payload_header( p_ctx, p_state, &data_track, &data_size ); + if(status != VC_CONTAINER_SUCCESS) break; + + if(data_track == track && ((p_state->stream_num >> 7) || !keyframe) && + !p_state->media_object_off) break; + + /* Skip payload */ + status = asf_read_next_payload(p_ctx, p_state, 0, &data_size ); + } + + return status; +} + +/*****************************************************************************/ +/* Helper for asf_reader_seek - seek when there is a top-level index (spec section 6.2) */ +static VC_CONTAINER_STATUS_T seek_by_top_level_index( + VC_CONTAINER_T *p_ctx, + int64_t *p_time, + VC_CONTAINER_SEEK_MODE_T mode, + VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned index; + uint64_t time = 0; + uint64_t block_address = module->top_level_index.blocks_offset; + uint64_t track_positions[ASF_TRACKS_MAX]; + + VC_CONTAINER_PARAM_UNUSED(mode); + LOG_DEBUG(p_ctx, "seek_by_top_level_index"); + + for (index = 0; index < ASF_TRACKS_MAX; ++index) + { + /* Set all to a stupid value */ + track_positions[index] = UINT64_MAX; + } + + /* Loop through the index blocks to find the one(s) that deal with the time(s) in question. + * Note that most ASF files only have one index block. */ + for (index = 0; index < module->top_level_index.block_count; ++index) + { + uint64_t block_duration, block_position; + uint32_t index_entry_count, stream; + LOG_DEBUG(p_ctx, "looking for index blocks at offset %"PRIu64, block_address); + status = SEEK(p_ctx, block_address); + if(status != VC_CONTAINER_SUCCESS) return status; + + /* Read the number of entries for this index block. */ + index_entry_count = READ_U32(p_ctx, "Index Entry Count"); + + /* Turn into a duration */ + block_duration = (uint64_t)index_entry_count * module->top_level_index.entry_time_interval; + + /* Go through each stream */ + for (stream = 0; stream < p_ctx->tracks_num; ++stream) + { + /* Work out the track's target time */ + uint64_t track_time = *p_time + module->preroll + module->time_offset; + + /* Have we the correct index block for the seek time? */ + if ((time <= track_time) && (track_time < time + block_duration)) + { + /* We have the correct index block for the seek time. Work out where in it. */ + uint32_t block_index = (track_time - time) / module->top_level_index.entry_time_interval; + uint64_t active_specifier = module->top_level_index.active_specifiers[stream]; + uint64_t new_position; + + /* Read the Block Positions value for the correct specifier */ + status = SEEK(p_ctx, + block_address + INT64_C(4) + + active_specifier * INT64_C(8)); + if (status != VC_CONTAINER_SUCCESS) + { + return status; + } + block_position = READ_U32(p_ctx, "Block Position"); + + /* Read the target address for the stream */ + status = SEEK(p_ctx, block_address + 4 /* skip index entry count */ + + (UINT64_C(8) * module->top_level_index.specifiers_count) /* block positions */ + + (UINT64_C(4) * module->top_level_index.specifiers_count * block_index) /* prior index entries */ + + (UINT64_C(4) * active_specifier)); /* correct specifier */ + LOG_DEBUG(p_ctx, "reading at %"PRIu64, STREAM_POSITION(p_ctx)); + + new_position = module->data_offset + block_position + (uint64_t)READ_U32(p_ctx, "Offset"); + LOG_DEBUG(p_ctx, "actual address for stream %"PRIu32" = %"PRIu64, stream, new_position); + track_positions[stream] = new_position; + } + } + + /* Work out where the next block is */ + block_address += (UINT64_C(8) * module->top_level_index.specifiers_count) + + (UINT64_C(4) * module->top_level_index.specifiers_count * index_entry_count); + } + + return seek_to_positions(p_ctx, track_positions, p_time, flags, 0, 0); +} + +/* Helper for asf_reader_seek - + * Given a set of positions seek the tracks. The status is the result of physically seeking each one. + * It is expected that the positions will be before *p_time; if the flags require it search + * for the next keyframe that is at or above *p_time. */ +static VC_CONTAINER_STATUS_T seek_to_positions(VC_CONTAINER_T *p_ctx, uint64_t track_positions[ASF_TRACKS_MAX], + int64_t *p_time, VC_CONTAINER_SEEK_FLAGS_T flags, + unsigned int start_track, bool seek_on_start_track) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint64_t global_position = UINT64_MAX; + unsigned int lowest_track, index, tracks; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + int64_t track_best_pts[ASF_TRACKS_MAX]; + + if (*p_time == 0) + { + // Special case: Time 0 means beginning of file. Don't search for the matching packet(s). + memset(&module->packet_state, 0, sizeof(module->packet_state)); + module->packet_state.start = track_positions[0]; + status = SEEK(p_ctx, module->packet_state.start); + + // Set each track to using the global state + for(index = 0; index < p_ctx->tracks_num; index++) + { + p_ctx->tracks[index]->priv->module->p_packet_state = &module->packet_state; + } + + return status; + } + + for(tracks = 0, index = start_track; tracks < p_ctx->tracks_num; + tracks++, index = (index+1) % p_ctx->tracks_num) + { + uint32_t data_size; + + /* Use an on-stack packet state. We can't use the global state, as we must leave it at + * the lowest position. We can't use any track's private state, as we will move it past + * the desired location. */ + ASF_PACKET_STATE private_state; + memset(&private_state, 0, sizeof(private_state)); + + track_best_pts[index] = INT64_MAX; + + status = SEEK(p_ctx, track_positions[index]); + + /* loop until we find the packet we're looking for. + * stop when we've seen a big enough PTS, and are on a key frame */ + while(status == VC_CONTAINER_SUCCESS) + { + /* Get the next key-frame */ + status = asf_reader_find_next_frame(p_ctx, index, &private_state, true, true); + if(status == VC_CONTAINER_SUCCESS) + { + /* Get the PTS, if any */ + int64_t pts = (int64_t)private_state.media_object_pts; + + if(pts != ASF_UNKNOWN_PTS) + { + if ((track_best_pts[index] == INT64_MAX) /* we don't have a time yet */ + || (pts <= *p_time) /* it's before our target */ + || (flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) /* we want after target */ + { + /* Store this time. It's the best yet. */ + track_best_pts[index] = pts; + + /* Update the desired position */ + track_positions[index] = private_state.start + private_state.current_offset; + + /* Copy the local state into this track's private state */ + p_ctx->tracks[index]->priv->module->local_packet_state = private_state; + + LOG_DEBUG(p_ctx, "seek forward track %u to pts %"PRIu64, + index, track_best_pts[index]); + } + + /* If we've got to our target time we can stop. */ + if (pts >= *p_time) + { + /* Then stop. */ + break; + } + } + + status = asf_read_next_payload(p_ctx, &private_state, 0, &data_size ); + } + } + + /* If we are seeking using a specific track, usually this is the video track + * and we want all the other tracks to start at the same time or later */ + if (seek_on_start_track && start_track == index) + { + flags |= VC_CONTAINER_SEEK_FLAG_FORWARD; + *p_time = track_best_pts[index]; + } + + { + ASF_PACKET_STATE *p_state = &p_ctx->tracks[index]->priv->module->local_packet_state; + + LOG_DEBUG(p_ctx, "seek track %u to pts %"PRIu64" (key:%i,moo:%i)", + index, track_best_pts[index], p_state->stream_num >> 7, p_state->media_object_off); + } + } + + /* Find the smallest track address in track_positions. This will be the global position */ + /* Also the lowest PTS in track_best_pts, this will be the new global PTS */ + for (index = 0, lowest_track = 0; index < p_ctx->tracks_num; ++index) + { + /* If it is smaller, remember it */ + if (track_positions[index] < global_position) + { + global_position = track_positions[index]; + lowest_track = index; + } + + /* Put the lowest PTS into entry 0 of the array */ + if ((track_best_pts[index] != INT64_MAX) && (track_best_pts[index] < track_best_pts[0])) + { + track_best_pts[0] = track_best_pts[index]; + } + } + + /* Update the caller with the lowest real PTS, if any. (we may have already done this above) */ + if (track_best_pts[0] != INT64_MAX) + { + *p_time = track_best_pts[0]; + } + else + { + LOG_DEBUG(p_ctx, "no PTS suitable to update the caller"); + } + + /* As we did an extra read on the index track past the desired location seek back to it */ + status = SEEK(p_ctx, global_position); + + /* Copy the packet state for the stream with the lowest address into the global state */ + module->packet_state = p_ctx->tracks[lowest_track]->priv->module->local_packet_state; + + for(index = 0; index < p_ctx->tracks_num; index++) + { + VC_CONTAINER_TRACK_MODULE_T* track_mod = p_ctx->tracks[index]->priv->module; + + /* If the track position is the global position, or it is invalid, use the global state */ + if ((track_positions[index] <= global_position) || (track_positions[index] == UINT64_MAX)) + { + track_mod->p_packet_state = &module->packet_state; + } + else + { + /* Track is not at the global position. Use the local state. */ + LOG_DEBUG(p_ctx, "track %u local position %"PRIu64, index, track_positions[index]); + track_mod->p_packet_state = &track_mod->local_packet_state; + } + } + + return status; +} + +/*****************************************************************************/ +/* Seek to a location in the file, using whatever indices are available + * If flags bit VC_CONTAINER_SEEK_FLAG_FORWARD is set the position is guaranteed to + * be a keyframe at or after the requested location. Conversely if it is not set + * the position is guaranteed to be at or before the request. */ +static VC_CONTAINER_STATUS_T asf_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *p_time, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_EOS; /* initialised to known fail state */ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int stream; + + VC_CONTAINER_PARAM_UNUSED(mode); + + LOG_DEBUG(p_ctx, "asf_reader_seek"); + + /* Prefer the top-level index to the simple index - it has byte offsets not packet offsets, + * and is likely to have seperate tables for every track */ + if (module->top_level_index.block_count) + { + status = seek_by_top_level_index(p_ctx, p_time, mode, flags); + } + else + { + uint64_t track_positions[ASF_TRACKS_MAX]; + int seek_track = -1; + uint32_t packet_num; + uint64_t new_position; + + if (*p_time == 0) + { + // Special optimisation - for time zero just go to the beginning. + packet_num = 0; + } + /* If there is a simple index use the packet number from it */ + else if (module->simple_index_track) + { + /* Correct time desired */ + uint64_t track_time = *p_time + module->preroll + module->time_offset; + + LOG_DEBUG(p_ctx, "using simple index"); + + /* Search the index for the correct packet */ + status = asf_reader_index_find_time(p_ctx, module->simple_index_track, track_time, + &packet_num, flags & VC_CONTAINER_SEEK_FLAG_FORWARD); + } + else + { + /* No index at all. Use arithmetic to guess the packet number. */ + LOG_DEBUG(p_ctx, "index not usable %u", (unsigned)status); + + if (module->packets_num == 0) + { + /* This is a broadcast stream, and we can't do the arithmetic. + * Set it to a value that will guarantee a seek fail. */ + LOG_DEBUG(p_ctx, "no packets in file"); + packet_num = UINT32_MAX; + } + else + { + packet_num = *p_time * module->packets_num / module->duration; + } + } + + /* calculate the byte address of the packet, relative to the start of data */ + new_position = (uint64_t)packet_num * (uint64_t)module->packet_size; + + LOG_DEBUG(p_ctx, "packet number %"PRIu32" approx byte offset %"PRIu64 , packet_num, new_position + module->data_offset); + if (new_position > (uint64_t)module->data_size) + { + new_position = module->data_size; + LOG_DEBUG(p_ctx, "arithmetic error, seeking to end of file %" PRIu64 , new_position + module->data_offset); + } + + new_position += module->data_offset; + + for(stream = 0; stream < p_ctx->tracks_num; stream++) + { + /* Use the 1st enabled video track as the seek stream */ + if(p_ctx->tracks[stream]->format->es_type == + VC_CONTAINER_ES_TYPE_VIDEO && + p_ctx->tracks[stream]->is_enabled && seek_track < 0) + seek_track = stream; + + track_positions[stream] = new_position; + } /* repeat for all tracks */ + + /* Work out if we actually got anywhere. If so, save the positions for the subsequent reads */ + status = seek_to_positions(p_ctx, track_positions, p_time, flags, + seek_track < 0 ? 0 : seek_track, seek_track >= 0); + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T asf_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + +/* FIXME: metadata is currently shared across all readers so freeing + it is left to the common layer but this isn't necessarily + the best solution. + for(i = 0; i meta_num; i++) + free(p_ctx->meta[i]); + if(p_ctx->meta_num) free(p_ctx->meta); + p_ctx->meta_num = 0; +*/ + for(i = 0; i < p_ctx->tracks_num; i++) + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + p_ctx->tracks_num = 0; + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T asf_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + unsigned int i; + GUID_T guid; + + /* Check for an ASF top-level header object */ + if(PEEK_BYTES(p_ctx, (uint8_t *)&guid, sizeof(guid)) < sizeof(guid) || + memcmp(&guid, &asf_guid_header, sizeof(guid))) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* + * We are dealing with an ASF file + */ + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + + /* Set the translation table to all error values */ + memset(&module->stream_number_to_index, 0xff, sizeof(module->stream_number_to_index)); + p_ctx->priv->module = module; + p_ctx->tracks = module->tracks; + + /* Read the top level header object */ + status = asf_read_object(p_ctx, INT64_C(0)); + if(status != VC_CONTAINER_SUCCESS) + goto error; + + /* Bail out if we didn't find a track */ + if(!p_ctx->tracks_num) {status = VC_CONTAINER_ERROR_NO_TRACK_AVAILABLE; goto error;} + + /* + * The top level data object must come next + */ + if(READ_GUID(p_ctx, &guid, "Object ID") != sizeof(guid) || + memcmp(&guid, &asf_guid_data, sizeof(guid))) + goto error; + + LOG_FORMAT(p_ctx, "Object Name: data"); + module->data_size = READ_U64(p_ctx, "Object Size"); + + /* If the data size was supplied remove the size of the common object header and the local header for this object */ + if(module->data_size) module->data_size -= ASF_OBJECT_HEADER_SIZE + 16 + 8 + 2; + + /* Sanity check the data object size */ + if(module->data_size < 0) + goto error; + + module->object_level++; + SKIP_GUID(p_ctx, "File ID"); + module->packets_num = READ_U64(p_ctx, "Total Data Packets"); + if(module->broadcast) module->packets_num = 0; + SKIP_U16(p_ctx, "Reserved"); + + if (module->packet_size) + { + LOG_DEBUG(p_ctx, "object size %"PRIu64" means %f packets", + module->data_size, (float)(module->data_size) / (float)(module->packet_size)); + } + + module->data_offset = STREAM_POSITION(p_ctx); + LOG_DEBUG(p_ctx, "expect end of data at address %"PRIu64, module->data_size + module->data_offset); + + module->object_level--; + + /* + * We now have all the information we really need to start playing the stream + */ + + /* Initialise state for all tracks */ + module->packet_state.start = module->data_offset; + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *p_track = p_ctx->tracks[i]; + p_track->priv->module->p_packet_state = &module->packet_state; + } + + p_ctx->priv->pf_close = asf_reader_close; + p_ctx->priv->pf_read = asf_reader_read; + p_ctx->priv->pf_seek = asf_reader_seek; + + if(STREAM_SEEKABLE(p_ctx)) + { + p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + p_ctx->capabilities |= VC_CONTAINER_CAPS_FORCE_TRACK; + } + + p_ctx->duration = module->duration; + + /* Check if we're done */ + if(!module->data_size || !STREAM_SEEKABLE(p_ctx)) + return VC_CONTAINER_SUCCESS; + + /* If the stream is seekable and not a broadcast stream, we want to use any index there + * might be at the end of the stream */ + + /* Seek back to the end of the data object */ + if( SEEK(p_ctx, module->data_offset + module->data_size) == VC_CONTAINER_SUCCESS) + { + /* This will catch the simple index object if it is there */ + do { + status = asf_read_object(p_ctx, INT64_C(0)); + } while(status == VC_CONTAINER_SUCCESS); + } + + for(i = 0; i < p_ctx->tracks_num; i++) + { + if(p_ctx->tracks[i]->priv->module->simple_index.offset) + LOG_DEBUG(p_ctx, "track %i has an index", i); + } + + /* Seek back to the start of the data */ + return SEEK(p_ctx, module->data_offset); + + error: + if(status == VC_CONTAINER_SUCCESS) status = VC_CONTAINER_ERROR_FORMAT_INVALID; + LOG_DEBUG(p_ctx, "asf: error opening stream (%i)", status); + if(module) asf_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open asf_reader_open +#endif diff --git a/containers/avi/CMakeLists.txt b/containers/avi/CMakeLists.txt new file mode 100644 index 0000000..f8589c1 --- /dev/null +++ b/containers/avi/CMakeLists.txt @@ -0,0 +1,19 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_avi ${LIBRARY_TYPE} avi_reader.c) + +target_link_libraries(reader_avi containers) + +install(TARGETS reader_avi DESTINATION ${VMCS_PLUGIN_DIR}) + +add_library(writer_avi ${LIBRARY_TYPE} avi_writer.c) + +target_link_libraries(writer_avi containers) + +install(TARGETS writer_avi DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/avi/avi_reader.c b/containers/avi/avi_reader.c new file mode 100644 index 0000000..4c4c3f4 --- /dev/null +++ b/containers/avi/avi_reader.c @@ -0,0 +1,1521 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#define CONTAINER_IS_LITTLE_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#define CONTAINER_HELPER_LOG_INDENT(a) 0 +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_waveformat.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define AVISF_DISABLED 0x00000001 /*< If set stream should not be enabled by default. */ +#define AVIF_MUSTUSEINDEX 0x00000020 +#define AVIF_TRUSTCKTYPE 0x00000800 /*< (OpenDML) keyframe information reliable. */ + +#define AVIIF_LIST 0x00000001 +#define AVIIF_KEYFRAME 0x00000010 +#define AVIIF_NOTIME 0x00000100 + +#define AVI_INDEX_OF_INDEXES 0x00 +#define AVI_INDEX_OF_CHUNKS 0x01 +#define AVI_INDEX_2FIELD 0x01 +#define AVI_INDEX_DELTAFRAME 0x80000000 + +#define AVI_TRACKS_MAX 16 /*< We won't try to handle streams with more tracks than this */ + +#define AVI_TWOCC(a,b) ((a) | (b << 8)) + +#define AVI_SYNC_CHUNK(ctx) \ + while(STREAM_POSITION(ctx) & 1) \ + { \ + if (SKIP_BYTES(ctx, 1) != 1) break; \ + } + +#define AVI_SKIP_CHUNK(ctx, size) \ + do { \ + SKIP_BYTES(ctx, size); \ + AVI_SYNC_CHUNK(ctx); \ + } while(0) + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct AVI_TRACK_STREAM_STATE_T +{ + unsigned current_track_num; /**< Number of track currently being read */ + int64_t data_offset; /**< Offset within the stream to find the track data */ + uint32_t chunk_size; /**< Size of the current chunk being read */ + uint32_t chunk_data_left; /**< Data left from the current chunk being read */ + + unsigned extra_chunk_track_num; /**< Temporary storage for in-band data e.g. 'dd' + chunks */ + uint32_t extra_chunk_data[4]; + uint32_t extra_chunk_data_offs; + uint32_t extra_chunk_data_len; +} AVI_TRACK_STREAM_STATE_T; + +typedef struct AVI_TRACK_CHUNK_STATE_T +{ + uint64_t index; + uint64_t offs; /**< offset into bytestream consisting of all chunks for this track */ + int64_t time_pos; /**< pts of chunk (if known) */ + uint32_t flags; /**< flags associated with chunk */ + AVI_TRACK_STREAM_STATE_T local_state; + AVI_TRACK_STREAM_STATE_T *state; +} AVI_TRACK_CHUNK_STATE_T; + +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + int64_t time_start; /**< i.e. 'dwStart' in 'strh' (converted to microseconds) */ + int64_t duration; /**< i.e. 'dwLength' in 'strh' (converted to microseconds) */ + uint32_t time_num; /**< i.e. 'dwScale' in 'strh' */ + uint32_t time_den; /**< i.e. 'dwRate' in 'strh', time_num / time_den = + samples (or frames) / second for audio (or video) */ + uint32_t sample_size; /**< i.e. 'dwSampleSize' in 'strh' */ + + uint64_t index_offset; /**< Offset to the start of an OpenDML index i.e. 'indx' + (if available) */ + uint32_t index_size; /**< Size of the OpenDML index chunk */ + AVI_TRACK_CHUNK_STATE_T chunk; +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *tracks[AVI_TRACKS_MAX]; + uint64_t data_offset; /**< Offset to the start of data packets i.e. + the data in the 'movi' list */ + uint64_t data_size; /**< Size of the chunk containing data packets */ + uint64_t index_offset; /**< Offset to the start of index data e.g. + the data in a 'idx1' list */ + uint32_t index_size; /**< Size of the chunk containing index data */ + AVI_TRACK_STREAM_STATE_T state; +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +static VC_CONTAINER_STATUS_T avi_find_chunk(VC_CONTAINER_T *p_ctx, VC_CONTAINER_FOURCC_T id, uint32_t *size) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t chunk_size; + + do { + chunk_id = READ_FOURCC(p_ctx, "Chunk ID"); + chunk_size = READ_U32(p_ctx, "Chunk size"); + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + + if(chunk_id == id) + { + *size = chunk_size; + return VC_CONTAINER_SUCCESS; + } + /* Not interested in this chunk, skip it. */ + AVI_SKIP_CHUNK(p_ctx, chunk_size); + } while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS); + + return status; /* chunk not found */ +} + +static VC_CONTAINER_STATUS_T avi_find_list(VC_CONTAINER_T *p_ctx, VC_CONTAINER_FOURCC_T fourcc, uint32_t *size) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t chunk_size; + uint32_t peek_buf[1]; + + do { + chunk_id = READ_FOURCC(p_ctx, "Chunk ID"); + chunk_size = READ_U32(p_ctx, "Chunk size"); + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + + if(chunk_id == VC_FOURCC('L','I','S','T')) + { + if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if (peek_buf[0] == fourcc) + { + *size = chunk_size; + return VC_CONTAINER_SUCCESS; + } + } + /* Not interested in this chunk, skip it. */ + AVI_SKIP_CHUNK(p_ctx, chunk_size); + } while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS); + + return status; /* list not found */ +} + +static int64_t avi_stream_ticks_to_us(VC_CONTAINER_TRACK_MODULE_T *track_module, uint64_t ticks) +{ + int64_t time; + vc_container_assert(track_module->time_den != 0); + time = INT64_C(1000000) * track_module->time_num * ticks / track_module->time_den; + return time; +} + +static int64_t avi_calculate_chunk_time(VC_CONTAINER_TRACK_MODULE_T *track_module) +{ + if (track_module->sample_size == 0) + return track_module->time_start + avi_stream_ticks_to_us(track_module, track_module->chunk.index); + else + return track_module->time_start + avi_stream_ticks_to_us(track_module, + ((track_module->chunk.offs + (track_module->sample_size >> 1)) / track_module->sample_size)); +} + +static VC_CONTAINER_STATUS_T avi_read_stream_header_list(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_TRACK_MODULE_T *track_module) +{ + VC_CONTAINER_STATUS_T status; + int64_t list_offset; + uint32_t list_size, chunk_id, chunk_size; + + int stream_header_chunk_read = 0, stream_format_chunk_read = 0; + + /* Look for a 'strl' LIST (sub)chunk */ + status = avi_find_list(p_ctx, VC_FOURCC('s','t','r','l'), &chunk_size); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "'strl' LIST not found for stream"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + list_offset = STREAM_POSITION(p_ctx); + list_size = chunk_size; + SKIP_FOURCC(p_ctx, "strl"); + + while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS && STREAM_POSITION(p_ctx) < list_offset + list_size) + { + int64_t offset = STREAM_POSITION(p_ctx); + chunk_id = READ_FOURCC(p_ctx, "Chunk ID"); + chunk_size = READ_U32(p_ctx, "Chunk size"); + LOG_FORMAT(p_ctx, "chunk %4.4s, offset: %"PRIi64", size: %i", (char *)&chunk_id, offset, chunk_size); + + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + + if(chunk_id == VC_FOURCC('s','t','r','h')) + { + VC_CONTAINER_FOURCC_T fourcc_type, fourcc_handler; + uint32_t flags, scale, rate, div, start, length, sample_size; + + /* We won't accept more than one 'strh' per stream */ + if (stream_header_chunk_read) + { + LOG_DEBUG(p_ctx, "rejecting invalid 'strl', found more than one 'strh'"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + fourcc_type = READ_FOURCC(p_ctx, "fccType"); + fourcc_handler = READ_FOURCC(p_ctx, "fccHandler"); + flags = READ_U32(p_ctx, "dwFlags"); + SKIP_U16(p_ctx, "wPriority"); + SKIP_U16(p_ctx, "wLanguage"); + SKIP_U32(p_ctx, "dwInitialFrames"); + scale = READ_U32(p_ctx, "dwScale"); + rate = READ_U32(p_ctx, "dwRate"); + start = READ_U32(p_ctx, "dwStart"); + length = READ_U32(p_ctx, "dwLength"); + SKIP_U32(p_ctx, "dwSuggestedBufferSize"); + SKIP_U32(p_ctx, "dwQuality"); + sample_size = READ_U32(p_ctx, "dwSampleSize"); + SKIP_U16(p_ctx, "rcFrame.left"); + SKIP_U16(p_ctx, "rcFrame.top"); + SKIP_U16(p_ctx, "rcFrame.right"); + SKIP_U16(p_ctx, "rcFrame.bottom"); + + /* In AVI, sec/frame = scale/rate and frames/sec = rate/scale */ + if (rate == 0) + { + LOG_DEBUG(p_ctx, "invalid dwRate: 0, using 1 as a guess"); + LOG_DEBUG(p_ctx, "timestamps will almost certainly be wrong"); + rate = 1; + } + + div = vc_container_maths_gcd((int64_t)scale, (int64_t)rate); + scale = scale / div; + rate = rate / div; + + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + if(fourcc_type == VC_FOURCC('v','i','d','s')) + { + track->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + track->format->type->video.frame_rate_num = rate; + track->format->type->video.frame_rate_den = scale; + + if (sample_size != 0) + { + LOG_DEBUG(p_ctx, "ignoring dwSampleSize (%d) for video stream", sample_size); + sample_size = 0; + } + } + else if(fourcc_type == VC_FOURCC('a','u','d','s')) + { + track->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + /* VBR audio is going to be non-framed */ + track->format->flags &= ~VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + } + else if(fourcc_type == VC_FOURCC('t','x','t','s')) + track->format->es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE; + + /* Don't overwrite any existing value (i.e. in the unlikely case where we + see 'strf' before 'strh') */ + if(!track->format->codec) track->format->codec = vfw_fourcc_to_codec(fourcc_handler); + + /* FIXME: enable this once read_media does the right thing */ + if (!(flags & AVISF_DISABLED) || 1) + track->is_enabled = 1; + + track_module->time_num = scale; + track_module->time_den = rate; + track_module->time_start = avi_stream_ticks_to_us(track_module, (uint64_t)start); + track_module->duration = avi_stream_ticks_to_us(track_module, (uint64_t)length); + track_module->sample_size = sample_size; + + p_ctx->duration = MAX(p_ctx->duration, track_module->duration); + + stream_header_chunk_read = 1; + } + else if(chunk_id == VC_FOURCC('s','t','r','f')) + { + uint8_t *buffer; + unsigned extra_offset = 0, extra_size = 0; + + /* We won't accept more than one 'strf' per stream */ + if (stream_format_chunk_read) + { + LOG_DEBUG(p_ctx, "rejecting invalid 'strl', found more than one 'strf'"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + /* Use the extradata buffer for reading in the entire 'strf' (should not be a large chunk) */ + if ((status = vc_container_track_allocate_extradata(p_ctx, track, chunk_size)) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "failed to allocate memory for 'strf' (%d bytes)", chunk_size); + return status; + } + + buffer = track->priv->extradata; + if(READ_BYTES(p_ctx, buffer, chunk_size) != chunk_size) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + AVI_SYNC_CHUNK(p_ctx); + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + status = vc_container_bitmapinfoheader_to_es_format(buffer, chunk_size, &extra_offset, &extra_size, track->format); + } + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + { + status = vc_container_waveformatex_to_es_format(buffer, chunk_size, &extra_offset, &extra_size, track->format); + if (track_module->sample_size != 0 && track_module->sample_size != track->format->type->audio.block_align) + { + LOG_DEBUG(p_ctx, "invalid dwSampleSize (%d), should match nBlockAlign (%d) for audio streams.", + track_module->sample_size, track->format->type->audio.block_align); + + /* Note that if nBlockAlign really is 0, strf is seriously broken... */ + if (track->format->type->audio.block_align != 0) + track_module->sample_size = track->format->type->audio.block_align; + } + else + { + /* Flawed muxers might only set nBlockAlign (i.e. not set dwSampleSize correctly). */ + if (track->format->type->audio.block_align == 1) + track_module->sample_size = 1; + } + } + + if (status != VC_CONTAINER_SUCCESS) return status; + + if (extra_size) + { + track->format->extradata = buffer + extra_offset; + track->format->extradata_size = extra_size; + } + + /* Codec specific fix-up */ + if (track->format->codec == VC_CONTAINER_CODEC_MP4A && + track->format->extradata_size) + { + /* This is going to be raw AAC so it will be framed */ + track_module->sample_size = 0; + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + } + + /* WMA specific fix-up */ + if ((track->format->codec == VC_CONTAINER_CODEC_WMA1 || + track->format->codec == VC_CONTAINER_CODEC_WMA2 || + track->format->codec == VC_CONTAINER_CODEC_WMAP || + track->format->codec == VC_CONTAINER_CODEC_WMAL || + track->format->codec == VC_CONTAINER_CODEC_WMAV) && + track->format->extradata_size) + { + track_module->sample_size = 0; + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + } + + stream_format_chunk_read = 1; + } + else if(chunk_id == VC_FOURCC('s','t','r','d')) + { + /* The data in a 'strd' chunk is either codec configuration data or DRM information, + we can safely assume it might be either as long as we don't overwrite any config + data read previously from a 'strf' chunk */ + if ((status = vc_container_track_allocate_drmdata(p_ctx, track, chunk_size)) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "failed to allocate memory for 'strd' (%d bytes)", chunk_size); + return status; + } + + if(READ_BYTES(p_ctx, track->priv->drmdata, chunk_size) != chunk_size) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + AVI_SYNC_CHUNK(p_ctx); + + if (!track->format->extradata) + { + if (vc_container_track_allocate_extradata(p_ctx, track, chunk_size) == VC_CONTAINER_SUCCESS) + { + memcpy(track->format->extradata, track->priv->drmdata, chunk_size); + + track->format->extradata = track->priv->extradata; + track->format->extradata_size = chunk_size; + } + else + { + LOG_DEBUG(p_ctx, "failed to allocate memory for 'strd' (%d bytes)", chunk_size); + LOG_DEBUG(p_ctx, "no codec configuration data set"); + } + } + } + else if(chunk_id == VC_FOURCC('i','n','d','x')) + { + track_module->index_offset = STREAM_POSITION(p_ctx); + track_module->index_size = chunk_size; + } + else + { + /* Not interested in this chunk, skip it. */ + } + + /* Skip any left-over data */ + AVI_SKIP_CHUNK(p_ctx, offset + chunk_size + 8 - STREAM_POSITION(p_ctx) ); + } + + if (!stream_header_chunk_read || !stream_format_chunk_read) + { + LOG_DEBUG(p_ctx, "invalid 'strl', 'strh' and 'strf' are both required"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + return status; +} + +static VC_CONTAINER_STATUS_T avi_find_next_data_chunk(VC_CONTAINER_T *p_ctx, uint32_t *id, uint32_t *size) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t chunk_size = 0; + uint32_t peek_buf[1]; + + do + { + chunk_id = READ_FOURCC(p_ctx, "Chunk ID"); + chunk_size = READ_U32(p_ctx, "Chunk size"); + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) + break; + + /* Check if this is a 'rec ' or a 'movi' LIST instead of a plain data chunk */ + if(chunk_id == VC_FOURCC('L','I','S','T')) + { + if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4) + return VC_CONTAINER_ERROR_EOS; + if (peek_buf[0] == VC_FOURCC('r','e','c',' ')) + SKIP_FOURCC(p_ctx, "rec "); + else if (peek_buf[0] == VC_FOURCC('m','o','v','i')) + SKIP_FOURCC(p_ctx, "movi"); + else + AVI_SKIP_CHUNK(p_ctx, chunk_size); /* Not interested in this LIST chunk, skip it. */ + continue; + } + + /* Check if this is a 'AVIX' RIFF header instead of a data chunk */ + if(chunk_id == VC_FOURCC('R','I','F','F')) + { + if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4) + return VC_CONTAINER_ERROR_EOS; + if (peek_buf[0] == VC_FOURCC('A','V','I','X')) + SKIP_FOURCC(p_ctx, "AVIX"); + else + AVI_SKIP_CHUNK(p_ctx, chunk_size); /* Not interested in this RIFF header, skip it. */ + continue; + } + + /* We treat only db/dc/dd or wb chunks as data */ + if((uint32_t)chunk_id >> 16 == AVI_TWOCC('d','c') || + (uint32_t)chunk_id >> 16 == AVI_TWOCC('d','b') || + (uint32_t)chunk_id >> 16 == AVI_TWOCC('d','d') || + (uint32_t)chunk_id >> 16 == AVI_TWOCC('w','b')) + { + *id = chunk_id; + *size = chunk_size; + break; + } + + /* Need to exit if a zero sized chunk encountered so we don't loop forever. */ + if(chunk_size == 0 && chunk_id == 0) return VC_CONTAINER_ERROR_EOS; + + /* Not interested in this chunk, skip it */ + AVI_SKIP_CHUNK(p_ctx, chunk_size); + } while ((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS); + + return status; +} + +static void avi_track_from_chunk_id(VC_CONTAINER_FOURCC_T chunk_id, uint16_t *data_type, uint16_t *track_num) +{ + *track_num = (((uint8_t*)&chunk_id)[0] - 48) * 10 + ((uint8_t*)&chunk_id)[1] - 48; + *data_type = (uint32_t)chunk_id >> 16; +} + +static VC_CONTAINER_STATUS_T avi_check_track(VC_CONTAINER_T *p_ctx, uint16_t data_type, uint16_t track_num) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + if (track_num < p_ctx->tracks_num) + { + if (data_type == AVI_TWOCC('w','b') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_AUDIO) + { + LOG_DEBUG(p_ctx, "suspicious track type ('wb'), track %d is not an audio track", track_num); + status = VC_CONTAINER_ERROR_FAILED; + } + if (data_type == AVI_TWOCC('d','b') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) + { + LOG_DEBUG(p_ctx, "suspicious track type ('db'), track %d is not a video track", track_num); + status = VC_CONTAINER_ERROR_FAILED; + } + if (data_type == AVI_TWOCC('d','c') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) + { + LOG_DEBUG(p_ctx, "suspicious track type ('dc'), track %d is not a video track", track_num); + status = VC_CONTAINER_ERROR_FAILED; + } + if (data_type == AVI_TWOCC('d','d') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) + { + LOG_DEBUG(p_ctx, "suspicious track type ('dd'), track %d is not a video track", track_num); + status = VC_CONTAINER_ERROR_FAILED; + } + } + else + { + LOG_DEBUG(p_ctx, "invalid track number %d (no more than %d tracks expected)", + track_num, p_ctx->tracks_num); + status = VC_CONTAINER_ERROR_FAILED; + } + + return status; +} + +static int avi_compare_seek_time(int64_t chunk_time, int64_t seek_time, + int chunk_is_keyframe, VC_CONTAINER_SEEK_FLAGS_T seek_flags) +{ + if (chunk_time == seek_time && chunk_is_keyframe && !(seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + return 0; + + if (chunk_time > seek_time && chunk_is_keyframe && (seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + return 0; + + if (chunk_time > seek_time && !(seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + return 1; /* Chunk time is past seek time, caller should use the previous keyframe */ + + return -1; +} + +static VC_CONTAINER_STATUS_T avi_scan_legacy_index_chunk(VC_CONTAINER_T *p_ctx, int seek_track_num, + int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_TRACK_MODULE_T *track_module; + AVI_TRACK_CHUNK_STATE_T selected_chunk; + int64_t base_offset = module->data_offset; + int64_t selected_chunk_offset = base_offset + 4; + int32_t extra_offset = 0; + int first_chunk_offset = 1; + uint64_t position; + + SEEK(p_ctx, module->index_offset); + memset(&selected_chunk, 0, sizeof(selected_chunk)); + + while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS && + (uint64_t)STREAM_POSITION(p_ctx) < module->index_offset + module->index_size) + { + VC_CONTAINER_FOURCC_T chunk_id; + uint16_t data_type, track_num; + uint32_t chunk_flags, offset, size; + + chunk_id = READ_FOURCC(p_ctx, "Chunk ID"); + chunk_flags = READ_U32(p_ctx, "dwFlags"); + offset = READ_U32(p_ctx, "dwOffset"); + size = READ_U32(p_ctx, "dwSize"); + + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) break; + + /* Although it's rare, the offsets might be given from the start of the file + instead of the data chunk, we have to handle both cases. */ + if (first_chunk_offset) + { + if (offset > module->data_offset) base_offset = INT64_C(0); + selected_chunk_offset = base_offset + 4; + first_chunk_offset = 0; + } + + avi_track_from_chunk_id(chunk_id, &data_type, &track_num); + LOG_DEBUG(p_ctx, "reading track %"PRIu16, track_num); + + if (avi_check_track(p_ctx, data_type, track_num) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "skipping index entry for track %d/%d", track_num, p_ctx->tracks_num); + continue; + } + + track_module = p_ctx->tracks[track_num]->priv->module; + + if (data_type == AVI_TWOCC('d','d')) + { + if (track_num == seek_track_num) + track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_ENCRYPTED; + extra_offset = -(size + 8); + } + + /* If this entry does not affect timing, skip it */ + if ((chunk_flags & (AVIIF_LIST | AVIIF_NOTIME)) || data_type == AVI_TWOCC('d','d')) + continue; + + position = base_offset + offset + extra_offset; + extra_offset = INT64_C(0); + + /* Check validity of position */ + if (position <= module->data_offset /* || (*pos > module->data_offset + module->data_size*/) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + if (track_num == seek_track_num) + { + bool is_keyframe = true; + int res; + + if (p_ctx->tracks[track_num]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + is_keyframe = chunk_flags & AVIIF_KEYFRAME; + + if (is_keyframe) + track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME; + else + track_module->chunk.flags &= ~(VC_CONTAINER_PACKET_FLAG_KEYFRAME); + + res = avi_compare_seek_time(track_module->chunk.time_pos, *time, is_keyframe, flags); + if (res > 0) + break; /* We've found the keyframe we wanted */ + + if (is_keyframe) + { + selected_chunk_offset = position; + selected_chunk = track_module->chunk; + } + + if (res == 0) + break; /* We've found the keyframe we wanted */ + + track_module->chunk.index++; + track_module->chunk.offs += size; + track_module->chunk.time_pos = avi_calculate_chunk_time(track_module); + + LOG_DEBUG(p_ctx, "index %"PRIu64", offs %"PRIu64", time %"PRIi64"us", track_module->chunk.index, + track_module->chunk.offs, track_module->chunk.time_pos); + } + } + + if (status == VC_CONTAINER_SUCCESS || + /* When seeking backwards, always return the last good position */ + !(flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + { + *pos = selected_chunk_offset; + track_module = p_ctx->tracks[seek_track_num]->priv->module; + track_module->chunk.index = selected_chunk.index; + track_module->chunk.offs = selected_chunk.offs; + track_module->chunk.flags = selected_chunk.flags; + track_module->chunk.time_pos = selected_chunk.time_pos; + *time = track_module->chunk.time_pos; + return VC_CONTAINER_SUCCESS; + } + + return VC_CONTAINER_ERROR_NOT_FOUND; +} + +static VC_CONTAINER_STATUS_T avi_scan_standard_index_chunk(VC_CONTAINER_T *p_ctx, uint64_t index_offset, + unsigned seek_track_num, int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND; + VC_CONTAINER_TRACK_MODULE_T *track_module = NULL; + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t chunk_size; + uint16_t data_type, track_num; + uint8_t index_type, index_sub_type; + uint32_t entry, entry_count = 0; + uint16_t entry_size; + uint64_t base_offset = UINT64_C(0); + uint64_t position = UINT64_C(0); + uint64_t prev_keyframe_offs = INT64_C(0); + AVI_TRACK_CHUNK_STATE_T prev_keyframe_chunk = { 0 }; + + SEEK(p_ctx, index_offset); + + chunk_id = READ_FOURCC(p_ctx, "Chunk ID"); + chunk_size = READ_U32(p_ctx, "Chunk Size"); + + entry_size = READ_U16(p_ctx, "wLongsPerEntry"); + index_sub_type = READ_U8(p_ctx, "bIndexSubType"); + index_type = READ_U8(p_ctx, "bIndexType"); + entry_count = READ_U32(p_ctx, "nEntriesInUse"); + chunk_id = READ_FOURCC(p_ctx, "dwChunkId"); + base_offset = READ_U64(p_ctx, "qwBaseOffset"); + SKIP_U32(p_ctx, "dwReserved"); + + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) + return status; + + avi_track_from_chunk_id(chunk_id, &data_type, &track_num); + status = avi_check_track(p_ctx, data_type, track_num); + if (status || chunk_size < 24 || track_num != seek_track_num) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + if (entry_size != 2 || index_sub_type != 0 || index_type != AVI_INDEX_OF_CHUNKS) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + entry_count = MIN(entry_count, (chunk_size - 24) / (entry_size * 4)); + + track_module = p_ctx->tracks[seek_track_num]->priv->module; + + for (entry = 0; entry < entry_count; ++entry) + { + uint32_t chunk_offset; + int key_frame = 0; + + chunk_offset = READ_U32(p_ctx, "dwOffset"); + chunk_size = READ_U32(p_ctx, "dwSize"); + + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) + break; + + status = VC_CONTAINER_ERROR_NOT_FOUND; + + if (!(chunk_size & AVI_INDEX_DELTAFRAME)) + key_frame = 1; + chunk_size &= ~AVI_INDEX_DELTAFRAME; + + position = base_offset + chunk_offset - 8; + + if (key_frame) + track_module->chunk.flags = VC_CONTAINER_PACKET_FLAG_KEYFRAME; + else + track_module->chunk.flags = 0; + + if (time != NULL) + { + int res; + status = VC_CONTAINER_ERROR_NOT_FOUND; + res = avi_compare_seek_time(track_module->chunk.time_pos, *time, key_frame, flags); + + if (res == 0) + { + *pos = position; + *time = track_module->chunk.time_pos; + status = VC_CONTAINER_SUCCESS; + break; + } + else if (res > 0) + { + if (prev_keyframe_offs) + { + *pos = prev_keyframe_offs; + track_module->chunk = prev_keyframe_chunk; + *time = track_module->chunk.time_pos; + status = VC_CONTAINER_SUCCESS; + } + break; + } + + if (key_frame) + { + prev_keyframe_offs = position; + prev_keyframe_chunk = track_module->chunk; + } + } + else + { + /* Not seeking to a time position, but scanning + track chunk state up to a certain file position + instead */ + if (position >= *pos) + { + status = VC_CONTAINER_SUCCESS; + break; + } + } + + track_module->chunk.index++; + track_module->chunk.offs += chunk_size; + track_module->chunk.time_pos = avi_calculate_chunk_time(track_module); + } + + return status; +} + +static VC_CONTAINER_STATUS_T avi_scan_super_index_chunk(VC_CONTAINER_T *p_ctx, unsigned index_track_num, + int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND; + VC_CONTAINER_FOURCC_T chunk_id; + uint64_t index_offset; + uint32_t index_size; + uint16_t data_type, track_num; + uint32_t entry, entry_count; + uint16_t entry_size; + uint8_t index_sub_type, index_type; + + index_offset = p_ctx->tracks[index_track_num]->priv->module->index_offset; + index_size = p_ctx->tracks[index_track_num]->priv->module->index_size; + + SEEK(p_ctx, index_offset); + + entry_size = READ_U16(p_ctx, "wLongsPerEntry"); + index_sub_type = READ_U8(p_ctx, "bIndexSubType"); + index_type = READ_U8(p_ctx, "bIndexType"); + entry_count = READ_U32(p_ctx, "nEntriesInUse"); + chunk_id = READ_FOURCC(p_ctx, "dwChunkId"); + SKIP_U32(p_ctx, "dwReserved0"); + SKIP_U32(p_ctx, "dwReserved1"); + SKIP_U32(p_ctx, "dwReserved2"); + + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) + return status; + + if (index_type == AVI_INDEX_OF_INDEXES) + { + avi_track_from_chunk_id(chunk_id, &data_type, &track_num); + status = avi_check_track(p_ctx, data_type, track_num); + if (status || index_size < 24 || track_num != index_track_num) return VC_CONTAINER_ERROR_FORMAT_INVALID; + + /* FIXME: We should probably support AVI_INDEX_2FIELD as well */ + if (entry_size != 4 || index_sub_type != 0) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + entry_count = MIN(entry_count, (index_size - 24) / entry_size); + + for (entry = 0; entry < entry_count; ++entry) + { + uint64_t entry_offset, standard_index_offset; + standard_index_offset = READ_U64(p_ctx, "qwOffset"); + SKIP_U32(p_ctx, "dwSize"); + SKIP_U32(p_ctx, "dwDuration"); + + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) + break; + + if (standard_index_offset == UINT64_C(0)) + { + status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; /* Not plausible */ + break; + } + + entry_offset = STREAM_POSITION(p_ctx); + status = avi_scan_standard_index_chunk(p_ctx, standard_index_offset, index_track_num, time, flags, pos); + if (status != VC_CONTAINER_ERROR_NOT_FOUND) break; + SEEK(p_ctx, entry_offset); /* Move to next entry ('ix' chunk); */ + } + } + else if (index_type == AVI_INDEX_OF_CHUNKS) + { + /* It seems we are dealing with a standard index instead... */ + status = avi_scan_standard_index_chunk(p_ctx, index_offset, index_track_num, time, flags, pos); + } + else + { + status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + return status; +} + +static VC_CONTAINER_STATUS_T avi_read_dd_chunk( VC_CONTAINER_T *p_ctx, + AVI_TRACK_STREAM_STATE_T *p_state, uint16_t data_type, uint32_t chunk_size, + uint16_t track_num ) +{ + if (data_type == AVI_TWOCC('d','d')) + { + if (p_state->extra_chunk_data_len || + chunk_size > sizeof(p_state->extra_chunk_data)) + { + LOG_DEBUG(p_ctx, "cannot handle multiple consecutive 'dd' chunks"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + if(READ_BYTES(p_ctx, p_state->extra_chunk_data, chunk_size) != chunk_size) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + AVI_SYNC_CHUNK(p_ctx); + p_state->extra_chunk_track_num = track_num; + p_state->extra_chunk_data_len = chunk_size; + p_state->extra_chunk_data_offs = 0; + + return VC_CONTAINER_ERROR_CONTINUE; + } + else if (p_state->extra_chunk_data_len && + p_state->extra_chunk_track_num != track_num) + { + LOG_DEBUG(p_ctx, "dropping data from '%02ddd' chunk, not for this track (%d)", + p_state->extra_chunk_track_num, track_num); + p_state->extra_chunk_data_len = 0; + } + + return VC_CONTAINER_SUCCESS; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ + +static VC_CONTAINER_STATUS_T avi_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = NULL; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + AVI_TRACK_STREAM_STATE_T *p_state = &module->state; + + if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) + { + p_state = p_ctx->tracks[p_packet->track]->priv->module->chunk.state; + } + + LOG_DEBUG(p_ctx, "seeking to %"PRIi64, p_state->data_offset); + SEEK(p_ctx, p_state->data_offset); + + if (p_state->chunk_data_left == 0) + { + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t chunk_size; + uint16_t data_type, track_num; + + if ((status = avi_find_next_data_chunk(p_ctx, &chunk_id, &chunk_size)) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "unable to find the next data chunk %d", status); + p_state->data_offset = STREAM_POSITION(p_ctx); + return status; + } + + avi_track_from_chunk_id(chunk_id, &data_type, &track_num); + + if (avi_check_track(p_ctx, data_type, track_num) != VC_CONTAINER_SUCCESS) + { + AVI_SKIP_CHUNK(p_ctx, chunk_size); + LOG_DEBUG(p_ctx, "skipping data for track %d/%d", track_num, p_ctx->tracks_num); + + p_state->data_offset = STREAM_POSITION(p_ctx); + return VC_CONTAINER_ERROR_CONTINUE; + } + + /* If we are reading from the global state (i.e. normal read or forced + read from the track on the global state), and the track we found is + not on the global state, connect the two */ + if (p_state == &module->state && + p_ctx->tracks[track_num]->priv->module->chunk.state != &module->state) + { + int64_t next_chunk; + + /* The track's offset is past the current position, skip it as we are + not interested in track data from before the track's offset. If we + were to read it we would return the same data multiple times. */ + next_chunk = (STREAM_POSITION(p_ctx) + chunk_size + 1) & ~1; + if (p_ctx->tracks[track_num]->priv->module->chunk.state->data_offset > next_chunk) + { + AVI_SKIP_CHUNK(p_ctx, chunk_size); + LOG_DEBUG(p_ctx, "skipping track %d/%d as we have already read it", track_num, p_ctx->tracks_num); + p_state->data_offset = STREAM_POSITION(p_ctx); + return VC_CONTAINER_ERROR_CONTINUE; + } + + /* The track state must be pointing to the current chunk. We need to + reconnect the track to the global state. */ + LOG_DEBUG(p_ctx, "reconnect track %u to the global state", track_num); + + p_ctx->tracks[track_num]->priv->module->chunk.state = &module->state; + + module->state = p_ctx->tracks[track_num]->priv->module->chunk.local_state; + + vc_container_assert(chunk_size >= p_state->chunk_data_left); + vc_container_assert(!p_state->chunk_data_left || + ((p_state->data_offset + p_state->chunk_data_left + 1) & ~1) == next_chunk); + vc_container_assert(p_state->current_track_num == track_num); + + return VC_CONTAINER_ERROR_CONTINUE; + } + + /* If we are not forcing, or if we are and found the track we are + interested in, check for dd data and set the track module for the later code */ + if (!(flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) || + (track_num == p_packet->track)) + { + if ((status = avi_read_dd_chunk(p_ctx, p_state, data_type, chunk_size, track_num)) != VC_CONTAINER_SUCCESS) + { + p_state->data_offset = STREAM_POSITION(p_ctx); + return status; + } + } + + p_state->chunk_size = p_state->chunk_data_left = chunk_size; + p_state->current_track_num = track_num; + } + + /* If there is data from another track skip past it */ + if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK && + p_state->current_track_num != p_packet->track) + { + p_state->data_offset = STREAM_POSITION(p_ctx); + + AVI_SKIP_CHUNK(p_ctx, p_state->chunk_data_left); + LOG_DEBUG(p_ctx, "skipping track %d/%d as we are ignoring it", + p_state->current_track_num, p_ctx->tracks_num); + + track_module = p_ctx->tracks[p_packet->track]->priv->module; + + /* Handle disconnection from global state */ + if (p_state == &module->state && + p_ctx->tracks[p_state->current_track_num]->priv->module->chunk.state == &module->state) + { + /* Make a copy of the global state */ + LOG_DEBUG(p_ctx, "using local state on track %d", p_packet->track); + track_module->chunk.local_state = module->state; + track_module->chunk.state = &track_module->chunk.local_state; + } + + track_module->chunk.state->data_offset = STREAM_POSITION(p_ctx); + track_module->chunk.state->chunk_data_left = 0; + + return VC_CONTAINER_ERROR_CONTINUE; + } + + track_module = p_ctx->tracks[p_state->current_track_num]->priv->module; + + if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) + { + vc_container_assert(p_state->current_track_num == p_packet->track); + } + + LOG_DEBUG(p_ctx, "reading track %u chunk at time %"PRIi64"us, offset %"PRIu64, + p_state->current_track_num, track_module->chunk.time_pos, + track_module->chunk.offs); + if (p_state->extra_chunk_data_len) + track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_ENCRYPTED; + else + track_module->chunk.flags &= ~VC_CONTAINER_PACKET_FLAG_ENCRYPTED; + + if (p_packet) + { + p_packet->track = p_state->current_track_num; + p_packet->size = p_state->chunk_data_left + + p_state->extra_chunk_data_len; + p_packet->flags = track_module->chunk.flags; + + if (p_state->chunk_data_left == p_state->chunk_size) + { + p_packet->pts = track_module->chunk.time_pos; + if (track_module->sample_size == 0) + p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME; + } + else + { + p_packet->pts = VC_CONTAINER_TIME_UNKNOWN; + if (track_module->sample_size == 0) + p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + } + + p_packet->dts = VC_CONTAINER_TIME_UNKNOWN; + } + + if (flags & VC_CONTAINER_READ_FLAG_SKIP) + { + SKIP_BYTES(p_ctx, p_state->chunk_data_left); + AVI_SYNC_CHUNK(p_ctx); + p_state->chunk_data_left = 0; + p_state->extra_chunk_data_len = 0; + } + + if (flags & VC_CONTAINER_READ_FLAG_INFO) + { + p_state->data_offset = STREAM_POSITION(p_ctx); + + LOG_DEBUG(p_ctx, "data position %"PRIi64, p_state->data_offset); + + return VC_CONTAINER_SUCCESS; + } + + if (p_packet) + { + uint8_t *data = p_packet->data; + uint32_t buffer_size = p_packet->buffer_size; + uint32_t size = 0; + uint32_t len; + + /* See if we need to insert extra data */ + if (p_state->extra_chunk_data_len) + { + len = MIN(buffer_size, p_state->extra_chunk_data_len); + memcpy(data, p_state->extra_chunk_data + p_state->extra_chunk_data_offs, len); + data += len; + buffer_size -= len; + size = len; + p_state->extra_chunk_data_len -= len; + p_state->extra_chunk_data_offs += len; + } + + /* Now try to read data into buffer */ + len = MIN(buffer_size, p_state->chunk_data_left); + READ_BYTES(p_ctx, data, len); + size += len; + p_state->chunk_data_left -= len; + p_packet->size = size; + + if (p_state->chunk_data_left) + p_packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + } + + if (p_state->chunk_data_left == 0) + { + AVI_SYNC_CHUNK(p_ctx); + track_module->chunk.index++; + track_module->chunk.offs += p_state->chunk_size; + track_module->chunk.flags = 0; + track_module->chunk.time_pos = avi_calculate_chunk_time(track_module); + } + + /* Update the track's position */ + p_state->data_offset = STREAM_POSITION(p_ctx); + + LOG_DEBUG(p_ctx, "data position %"PRIi64, p_state->data_offset); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *p_offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint64_t position, pos; + AVI_TRACK_CHUNK_STATE_T chunk_state[AVI_TRACKS_MAX]; + AVI_TRACK_STREAM_STATE_T global_state; + unsigned seek_track_num, i; + + if (mode != VC_CONTAINER_SEEK_MODE_TIME || !STREAM_SEEKABLE(p_ctx)) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + LOG_DEBUG(p_ctx, "AVI seeking to %"PRIi64"us", *p_offset); + + /* Save current position and chunk state so we can restore it if we + hit an error whilst scanning index data */ + position = STREAM_POSITION(p_ctx); + for(i = 0; i < p_ctx->tracks_num; i++) + chunk_state[i] = p_ctx->tracks[i]->priv->module->chunk; + global_state = p_ctx->priv->module->state; + + /* Clear track state affected by a seek operation of any kind */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + p_ctx->tracks[i]->priv->module->chunk.index = INT64_C(0); + p_ctx->tracks[i]->priv->module->chunk.offs = INT64_C(0); + p_ctx->tracks[i]->priv->module->chunk.flags = 0; + p_ctx->tracks[i]->priv->module->chunk.time_pos = p_ctx->tracks[i]->priv->module->time_start; + p_ctx->tracks[i]->priv->module->chunk.state = &p_ctx->tracks[i]->priv->module->chunk.local_state; + p_ctx->tracks[i]->priv->module->chunk.local_state.chunk_data_left = UINT64_C(0); + p_ctx->tracks[i]->priv->module->chunk.local_state.chunk_size = UINT64_C(0); + p_ctx->tracks[i]->priv->module->chunk.local_state.extra_chunk_data_len = 0; + p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = module->data_offset + 4; + } + + /* Clear the global state */ + p_ctx->priv->module->state.chunk_data_left = UINT64_C(0); + p_ctx->priv->module->state.chunk_size = UINT64_C(0); + p_ctx->priv->module->state.extra_chunk_data_len = 0; + p_ctx->priv->module->state.data_offset = module->data_offset + 4; + + /* Choose track to use for seeking, favor video tracks and tracks + that are enabled */ + for(i = 0; i < p_ctx->tracks_num; i++) + if(p_ctx->tracks[i]->is_enabled && + p_ctx->tracks[i]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) break; + if(i == p_ctx->tracks_num) + for(i = 0; i < p_ctx->tracks_num; i++) + if(p_ctx->tracks[i]->is_enabled) break; + if(i == p_ctx->tracks_num) i = 0; + + LOG_DEBUG(p_ctx, "seek on track %d/%d", i, p_ctx->tracks_num); + seek_track_num = i; + + if (p_ctx->tracks[seek_track_num]->priv->module->index_offset) + { + LOG_DEBUG(p_ctx, "seeking using the super index"); + status = avi_scan_super_index_chunk(p_ctx, seek_track_num, p_offset, flags, &pos); + if (status != VC_CONTAINER_SUCCESS) goto error; + + /* As AVI chunks don't convey timestamp information, we need to scan all tracks + to the seek file position */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + if (p_ctx->tracks[i]->priv->module->index_offset && i != seek_track_num) + { + uint64_t track_pos; + int64_t track_time = *p_offset; + + status = avi_scan_super_index_chunk(p_ctx, i, &track_time, flags, &track_pos); + if (status != VC_CONTAINER_SUCCESS) goto error; + p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = track_pos; + } + } + } + else + { + LOG_DEBUG(p_ctx, "seeking using the legacy index"); + + /* The legacy index comes after data so it might not have been available at the + time the container was opened; if this is the case, see if we can find an index + now, if we can't, then there's no way we can proceed with the seek. */ + if(!module->index_offset) + { + uint32_t chunk_size; + + LOG_DEBUG(p_ctx, "no index offset, searching for one"); + + /* Locate data chunk and skip it */ + SEEK(p_ctx, module->data_offset); + AVI_SKIP_CHUNK(p_ctx, module->data_size); + /* Now search for the index */ + status = avi_find_chunk(p_ctx, VC_FOURCC('i','d','x','1'), &chunk_size); + if (status == VC_CONTAINER_SUCCESS) + { + /* Store offset to index data */ + module->index_offset = STREAM_POSITION(p_ctx); + module->index_size = chunk_size; + p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX; + p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG; + } + } + /* Check again, we may or may not have an index */ + if (!module->index_offset) + { + /* If there is no index and we are seeking to 0 we can assume the + correct location is the start of the data. Otherwise we are unable + to seek to a specified non-zero location without an index */ + if (*p_offset != INT64_C(0)) + { + LOG_DEBUG(p_ctx, "failed to find the legacy index, unable to seek"); + status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + goto error; + } + pos = module->data_offset; + } + else + { + LOG_DEBUG(p_ctx, "scanning the legacy index chunk"); + status = avi_scan_legacy_index_chunk(p_ctx, seek_track_num, p_offset, flags, &pos); + if (status != VC_CONTAINER_SUCCESS) goto error; + + for (i = 0; i < p_ctx->tracks_num; i++) + { + if (i != seek_track_num) + { + uint64_t track_pos = pos; + int64_t track_time = *p_offset; + + status = avi_scan_legacy_index_chunk(p_ctx, i, &track_time, flags, &track_pos); + if (status != VC_CONTAINER_SUCCESS) goto error; + p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = track_pos; + p_ctx->tracks[i]->priv->module->chunk.local_state.current_track_num = i; + } + } + } + } + + position = pos; + + /* Set the seek track's data offset */ + p_ctx->tracks[seek_track_num]->priv->module->chunk.local_state.data_offset = position; + p_ctx->tracks[seek_track_num]->priv->module->chunk.local_state.current_track_num = seek_track_num; + + /* Connect the earlier track(s) to the global state. Needs 2 passes */ + module->state.data_offset = INT64_MAX; + for(i = 0; i < p_ctx->tracks_num; i++) + { + if(p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset < + module->state.data_offset) + module->state = p_ctx->tracks[i]->priv->module->chunk.local_state; + } + for(i = 0; i < p_ctx->tracks_num; i++) + { + if(p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset == + module->state.data_offset) + p_ctx->tracks[i]->priv->module->chunk.state = &module->state; + } + + LOG_DEBUG(p_ctx, "seek to %"PRIi64", position %"PRIu64, *p_offset, pos); + + return SEEK(p_ctx, position); + +error: + p_ctx->priv->module->state = global_state; + for(i = 0; i < p_ctx->tracks_num; i++) + p_ctx->tracks[i]->priv->module->chunk = chunk_state[i]; + SEEK(p_ctx, position); + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + + for(i = 0; i < p_ctx->tracks_num; i++) + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + p_ctx->tracks = NULL; + p_ctx->tracks_num = 0; + free(module); + p_ctx->priv->module = 0; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + uint32_t chunk_size; + uint32_t peek_buf[3]; + unsigned int i; + uint32_t flags, num_streams; + int64_t offset; + + /* Check the RIFF chunk descriptor */ + if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 12) != 12) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if( peek_buf[0] != VC_FOURCC('R','I','F','F') ) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if( peek_buf[2] != VC_FOURCC('A','V','I',' ') ) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* + * We now know we are dealing with an AVI file + */ + SKIP_FOURCC(p_ctx, "RIFF ID"); + SKIP_U32(p_ctx, "fileSize"); + SKIP_FOURCC(p_ctx, "fileType"); + + /* Look for the 'hdrl' LIST (sub)chunk */ + status = avi_find_list(p_ctx, VC_FOURCC('h','d','r','l'), &chunk_size); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "'hdrl' LIST not found"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + SKIP_FOURCC(p_ctx, "hdrl"); + + /* Now look for the 'avih' sub-chunk */ + status = avi_find_chunk(p_ctx, VC_FOURCC('a','v','i','h'), &chunk_size); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "'avih' not found"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + /* Parse the 'avih' sub-chunk */ + SKIP_U32(p_ctx, "dwMicroSecPerFrame"); + SKIP_U32(p_ctx, "dwMaxBytesPerSec"); + SKIP_U32(p_ctx, "dwPaddingGranularity"); + flags = READ_U32(p_ctx, "dwFlags"); + SKIP_U32(p_ctx, "dwTotalFrames"); + SKIP_U32(p_ctx, "dwInitialFrames"); + num_streams = READ_U32(p_ctx, "dwStreams"); + SKIP_U32(p_ctx, "dwSuggestedBufferSize"); + SKIP_U32(p_ctx, "dwWidth"); + SKIP_U32(p_ctx, "dwHeight"); + SKIP_U32(p_ctx, "dwReserved0"); + SKIP_U32(p_ctx, "dwReserved1"); + SKIP_U32(p_ctx, "dwReserved2"); + SKIP_U32(p_ctx, "dwReserved3"); + + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) + goto error; + + /* Allocate our context and tracks */ + if ((module = malloc(sizeof(*module))) == NULL) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = module->tracks; + + if (num_streams > AVI_TRACKS_MAX) + { + LOG_DEBUG(p_ctx, "cannot handle %u tracks, restricted to %d", num_streams, AVI_TRACKS_MAX); + num_streams = AVI_TRACKS_MAX; + } + + for (p_ctx->tracks_num = 0; p_ctx->tracks_num != num_streams; p_ctx->tracks_num++) + { + p_ctx->tracks[p_ctx->tracks_num] = vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(!p_ctx->tracks[p_ctx->tracks_num]) break; + } + if(p_ctx->tracks_num != num_streams) + { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + + /* Try to read stream header list chunks ('strl') */ + for (i = 0; i != num_streams; ++i) + { + status = avi_read_stream_header_list(p_ctx, p_ctx->tracks[i], p_ctx->tracks[i]->priv->module); + if(status != VC_CONTAINER_SUCCESS) goto error; + } + + /* Look for the 'movi' LIST (sub)chunk */ + status = avi_find_list(p_ctx, VC_FOURCC('m','o','v','i'), &chunk_size); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "'movi' LIST not found"); + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + goto error; + } + + /* Store offset to the start and size of data (the 'movi' LIST) */ + module->data_offset = STREAM_POSITION(p_ctx); + module->data_size = chunk_size; + + p_ctx->priv->pf_close = avi_reader_close; + p_ctx->priv->pf_read = avi_reader_read; + p_ctx->priv->pf_seek = avi_reader_seek; + + if (flags & AVIF_MUSTUSEINDEX) + { + LOG_DEBUG(p_ctx, "AVIF_MUSTUSEINDEX not supported, playback might not work properly"); + } + + /* If the stream is seekable, see if we can find an index (for at + least one of the tracks); even if we cannot find an index now, + one might become available later (e.g. when the stream grows + run-time), in that case we might want to report that we can seek + and re-search for the index again if or when we're requested to + seek. */ + if(STREAM_SEEKABLE(p_ctx)) + { + p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + p_ctx->capabilities |= VC_CONTAINER_CAPS_FORCE_TRACK; + + for(i = 0; i < p_ctx->tracks_num; i++) + if(p_ctx->tracks[i]->priv->module->index_offset) break; + + if (i < p_ctx->tracks_num) + { + p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX; + if (flags & AVIF_TRUSTCKTYPE) + p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG; + } + else + { + /* Skip data first */ + AVI_SKIP_CHUNK(p_ctx, module->data_size); + /* Now search for the index */ + status = avi_find_chunk(p_ctx, VC_FOURCC('i','d','x','1'), &chunk_size); + if (status == VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "'idx1' found"); + /* Store offset to index data */ + module->index_offset = STREAM_POSITION(p_ctx); + module->index_size = chunk_size; + p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX; + p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG; + } + + /* Seek back to the start of the data */ + SEEK(p_ctx, module->data_offset); + } + } + + SKIP_FOURCC(p_ctx, "movi"); + + for (i = 0; i != num_streams; i++) + { + p_ctx->tracks[i]->priv->module->chunk.state = &p_ctx->priv->module->state; + } + p_ctx->priv->module->state.data_offset = STREAM_POSITION(p_ctx); + + /* Update the tracks to set their data offsets. This help with bad + interleaving, for example when there is all the video tracks followed + by all the audio tracks. It means we don't have to read through the + tracks we are not interested in when forcing a read from a given track, + as could be the case in the above example. If this fails we will fall + back to skipping track data. */ + offset = INT64_C(0); + avi_reader_seek(p_ctx, &offset, VC_CONTAINER_SEEK_MODE_TIME, VC_CONTAINER_SEEK_FLAG_PRECISE); + + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) goto error; + return VC_CONTAINER_SUCCESS; + +error: + LOG_DEBUG(p_ctx, "error opening stream (%i)", status); + for(i = 0; i < p_ctx->tracks_num; i++) + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + p_ctx->tracks = NULL; + p_ctx->tracks_num = 0; + if (module) free(module); + p_ctx->priv->module = NULL; + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open avi_reader_open +#endif diff --git a/containers/avi/avi_writer.c b/containers/avi/avi_writer.c new file mode 100644 index 0000000..589ee13 --- /dev/null +++ b/containers/avi/avi_writer.c @@ -0,0 +1,1171 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include + +#define CONTAINER_IS_LITTLE_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#define CONTAINER_HELPER_LOG_INDENT(a) 0 +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_writer_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T avi_writer_open( VC_CONTAINER_T *p_ctx ); + +/****************************************************************************** +Defines. +******************************************************************************/ +#define AVISF_DISABLED 0x00000001 /*< If set stream should not be enabled by default. */ +#define AVIF_HASINDEX 0x00000010 +#define AVIF_TRUSTCKTYPE 0x00000800 +#define AVIIF_KEYFRAME 0x00000010 + +#define AVI_INDEX_OF_INDEXES 0x00 +#define AVI_INDEX_OF_CHUNKS 0x01 +#define AVI_INDEX_DELTAFRAME 0x80000000 + +#define AVI_INDEX_ENTRY_SIZE 16 +#define AVI_SUPER_INDEX_ENTRY_SIZE 16 +#define AVI_STD_INDEX_ENTRY_SIZE 8 +#define AVI_FRAME_BUFFER_SIZE 100000 + +#define AVI_TRACKS_MAX 3 + +#define AVI_AUDIO_CHUNK_SIZE_LIMIT 16384 /*< Watermark limit for data chunks when 'dwSampleSize' + is non-zero */ + +#define AVI_END_CHUNK(ctx) \ + do { \ + if(STREAM_POSITION(ctx) & 1) WRITE_U8(ctx, 0, "AVI_END_CHUNK"); \ + } while(0) + +#define AVI_PACKET_KEYFRAME (VC_CONTAINER_PACKET_FLAG_KEYFRAME | VC_CONTAINER_PACKET_FLAG_FRAME_END) +#define AVI_PACKET_IS_KEYFRAME(flags) (((flags) & AVI_PACKET_KEYFRAME) == AVI_PACKET_KEYFRAME) + +/****************************************************************************** +Type definitions. +******************************************************************************/ +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + uint32_t chunk_index; /**< index of current chunk */ + uint32_t chunk_offs; /**< current offset into bytestream consisting of all + chunks for this track */ + uint32_t sample_size; /**< i.e. 'dwSampleSize' in 'strh' */ + uint32_t max_chunk_size; /**< largest chunk written so far */ + uint64_t index_offset; /**< Offset to the start of an OpenDML index for this track + i.e. 'indx' */ + uint32_t index_size; /**< Size of the OpenDML index for this track i.e. 'indx' */ +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *tracks[AVI_TRACKS_MAX]; + VC_CONTAINER_WRITER_EXTRAIO_T null_io; /**< Null I/O for calculating chunk sizes, etc. */ + VC_CONTAINER_WRITER_EXTRAIO_T temp_io; /**< I/O for temporary storage of index data */ + int headers_written; + + uint32_t header_list_offset; /**< Offset to the header list chunk ('hdrl') */ + uint32_t header_list_size; /**< Size of the header list chunk ('hdrl') */ + uint32_t data_offset; /**< Offset to the start of data packets i.e. + the data in the AVI RIFF 'movi' list */ + uint64_t data_size; /**< Size of the chunk containing data packets */ + uint32_t index_offset; /**< Offset to the start of index data e.g. + the data in an 'idx1' list */ + unsigned current_track_num; /**< Number of track currently being written */ + uint32_t chunk_size; /**< Final size of the current chunk being written (if known) */ + uint32_t chunk_data_written; /**< Data written to the current chunk so far */ + uint8_t *avi_frame_buffer; /**< For accumulating whole frames when seeking isn't available. */ + VC_CONTAINER_PACKET_T frame_packet; /**< Packet header for whole frame. */ + + VC_CONTAINER_STATUS_T index_status; +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Local Functions +******************************************************************************/ +static void avi_chunk_id_from_track_num( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_FOURCC_T *p_chunk_id, unsigned int track_num ) +{ + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[track_num]; + VC_CONTAINER_FOURCC_T chunk_id = 0; + char track_num_buf[3]; + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + chunk_id = VC_FOURCC('0','0','d','c'); + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + chunk_id = VC_FOURCC('0','0','w','b'); + else + { + /* Note that avi_writer_add_track should ensure this + can't happen */ + *p_chunk_id = VC_FOURCC('J','U','N','K'); return; + } + + snprintf(track_num_buf, sizeof(track_num_buf), "%02d", track_num); + memcpy(&chunk_id, track_num_buf, 2); + + *p_chunk_id = chunk_id; +} + +/*****************************************************************************/ +static void avi_index_chunk_id_from_track_num(VC_CONTAINER_FOURCC_T *p_chunk_id, + unsigned int track_num ) +{ + VC_CONTAINER_FOURCC_T chunk_id = 0; + char track_num_buf[3]; + + chunk_id = VC_FOURCC('i','x','0','0'); + + snprintf(track_num_buf, sizeof(track_num_buf), "%02d", track_num); + memcpy(((uint8_t*)&chunk_id) + 2, track_num_buf, 2); + + *p_chunk_id = chunk_id; +} + +/*****************************************************************************/ +static uint32_t avi_num_chunks( VC_CONTAINER_T *p_ctx ) +{ + unsigned int i; + uint32_t num_chunks = 0; + for (i = 0; i < p_ctx->tracks_num; i++) + num_chunks += p_ctx->tracks[i]->priv->module->chunk_index; + + return num_chunks; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_finish_data_chunk( VC_CONTAINER_T *p_ctx, uint32_t chunk_size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + if (chunk_size) + { + /* Rewrite the chunk size, this won't be efficient if it happens often */ + if (STREAM_SEEKABLE(p_ctx)) + { + SEEK(p_ctx, STREAM_POSITION(p_ctx) - chunk_size - 4); + WRITE_U32(p_ctx, chunk_size, "Chunk Size"); + SKIP_BYTES(p_ctx, chunk_size); + } + else + { + LOG_DEBUG(p_ctx, "warning, can't rewrite chunk size, data will be malformed"); + status = VC_CONTAINER_ERROR_FAILED; + } + } + + AVI_END_CHUNK(p_ctx); + + if (status != VC_CONTAINER_SUCCESS) status = STREAM_STATUS(p_ctx); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_index_entry( VC_CONTAINER_T *p_ctx, uint8_t track_num, + uint32_t chunk_size, int keyframe ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t deltaframe = keyframe ? 0 : AVI_INDEX_DELTAFRAME; + + vc_container_io_write_uint8(module->temp_io.io, track_num); + vc_container_io_write_be_uint32(module->temp_io.io, chunk_size | deltaframe); + + if (module->temp_io.io->status != VC_CONTAINER_SUCCESS) + { + module->index_status = module->temp_io.io->status; + LOG_DEBUG(p_ctx, "warning, couldn't store index data, index data will be incorrect"); + } + + return module->temp_io.io->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_read_index_entry( VC_CONTAINER_T *p_ctx, + unsigned int *p_track_num, uint32_t *p_chunk_size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t chunk_size; + uint8_t track_num; + + track_num = vc_container_io_read_uint8(module->temp_io.io); + chunk_size = vc_container_io_read_be_uint32(module->temp_io.io); + + /* This shouldn't really happen if the temporary I/O is reliable */ + if (track_num >= p_ctx->tracks_num) return VC_CONTAINER_ERROR_FAILED; + + *p_track_num = track_num; + *p_chunk_size = chunk_size; + + return module->temp_io.io->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_stream_format_chunk(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, uint32_t chunk_size) +{ + VC_CONTAINER_STATUS_T status; + + WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','f'), "Chunk ID"); + WRITE_U32(p_ctx, chunk_size, "Chunk Size"); + + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + status = vc_container_write_bitmapinfoheader(p_ctx, track->format); + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + status = vc_container_write_waveformatex(p_ctx, track->format); + + if (status != VC_CONTAINER_SUCCESS) return status; + + AVI_END_CHUNK(p_ctx); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_stream_header_chunk(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track) +{ + VC_CONTAINER_FOURCC_T fourcc_type = 0, fourcc_handler = 0; + uint32_t flags, scale = 0, rate = 0, div, start = 0, sample_size = 0; + uint16_t left = 0, right = 0, top = 0, bottom = 0; + uint32_t max_chunk_size, length = 0; + + WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','h'), "Chunk ID"); + WRITE_U32(p_ctx, 56, "Chunk Size"); + + if (!track->is_enabled) + flags = 0; /* AVISF_DISABLED; FIXME: write_media should set this correctly! */ + else + flags = 0; + + if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + fourcc_type = VC_FOURCC('v','i','d','s'); + sample_size = 0; + scale = track->format->type->video.frame_rate_den; + rate = track->format->type->video.frame_rate_num; + if (rate == 0 || scale == 0) + { + LOG_DEBUG(p_ctx, "invalid video framerate (%d/%d)", rate, scale); + LOG_DEBUG(p_ctx, "using 30/1 (playback timing will almost certainly be incorrect)"); + scale = 1; rate = 30; + } + + top = track->format->type->video.y_offset; + left = track->format->type->video.x_offset; + bottom = track->format->type->video.y_offset + track->format->type->video.visible_height; + right = track->format->type->video.x_offset + track->format->type->video.visible_width; + } + else if (track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + { + fourcc_type = VC_FOURCC('a','u','d','s'); + sample_size = track->format->type->audio.block_align; + scale = 1; + + if (track->format->type->audio.block_align) + rate = (track->format->bitrate / track->format->type->audio.block_align) >> 3; + + if (rate == 0) + { + rate = track->format->type->audio.sample_rate ? track->format->type->audio.sample_rate : 32000; + LOG_DEBUG(p_ctx, "invalid audio rate, using %d (playback timing will almost certainly be incorrect)", + rate); + } + } + else + { + /* avi_writer_add_track should ensure this can't happen */ + vc_container_assert(0); + } + + fourcc_handler = codec_to_vfw_fourcc(track->format->codec); + + div = vc_container_maths_gcd((int64_t)scale, (int64_t)rate); + scale /= div; + rate /= div; + + length = sample_size ? track->priv->module->chunk_offs : track->priv->module->chunk_index; + max_chunk_size = track->priv->module->max_chunk_size; + track->priv->module->sample_size = sample_size; + + WRITE_FOURCC(p_ctx, fourcc_type, "fccType"); + WRITE_FOURCC(p_ctx, fourcc_handler, "fccHandler"); + WRITE_U32(p_ctx, flags, "dwFlags"); + WRITE_U16(p_ctx, 0, "wPriority"); + WRITE_U16(p_ctx, 0, "wLanguage"); + WRITE_U32(p_ctx, 0, "dwInitialFrames"); + WRITE_U32(p_ctx, scale, "dwScale"); + WRITE_U32(p_ctx, rate, "dwRate"); + WRITE_U32(p_ctx, start, "dwStart"); + WRITE_U32(p_ctx, length, "dwLength"); + WRITE_U32(p_ctx, max_chunk_size, "dwSuggestedBufferSize"); + WRITE_U32(p_ctx, 0, "dwQuality"); + WRITE_U32(p_ctx, sample_size, "dwSampleSize"); + WRITE_U16(p_ctx, left, "rcFrame.left"); + WRITE_U16(p_ctx, top, "rcFrame.top"); + WRITE_U16(p_ctx, right, "rcFrame.right"); + WRITE_U16(p_ctx, bottom, "rcFrame.bottom"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_super_index_chunk(VC_CONTAINER_T *p_ctx, unsigned int index_track_num, + uint32_t index_size) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[index_track_num]->priv->module; + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t num_indices = 1; /* FIXME: support for multiple RIFF chunks (AVIX) */ + unsigned int i; + + if(module->null_io.refcount) + { + /* Assume that we're not actually writing the data, just want know the index chunk size */ + WRITE_BYTES(p_ctx, NULL, 8 + 24 + num_indices * (int64_t)AVI_SUPER_INDEX_ENTRY_SIZE); + return STREAM_STATUS(p_ctx); + } + + if (track_module->index_offset) + WRITE_FOURCC(p_ctx, VC_FOURCC('i','n','d','x'), "Chunk ID"); + else + WRITE_FOURCC(p_ctx, VC_FOURCC('J','U','N','K'), "Chunk ID"); + + WRITE_U32(p_ctx, index_size, "Chunk Size"); + + avi_chunk_id_from_track_num(p_ctx, &chunk_id, index_track_num); + WRITE_U16(p_ctx, 4, "wLongsPerEntry"); + WRITE_U8(p_ctx, 0, "bIndexSubType"); + WRITE_U8(p_ctx, AVI_INDEX_OF_INDEXES, "bIndexType"); + WRITE_U32(p_ctx, num_indices, "nEntriesInUse"); + WRITE_FOURCC(p_ctx, chunk_id, "dwChunkId"); + WRITE_U32(p_ctx, 0, "dwReserved0"); + WRITE_U32(p_ctx, 0, "dwReserved1"); + WRITE_U32(p_ctx, 0, "dwReserved2"); + + for (i = 0; i < num_indices; ++i) + { + uint64_t index_offset = track_module->index_offset; + uint32_t chunk_size = track_module->index_size; + uint32_t length = track_module->sample_size ? + track_module->chunk_offs : track_module->chunk_index; + WRITE_U64(p_ctx, index_offset, "qwOffset"); + WRITE_U32(p_ctx, chunk_size, "dwSize"); + WRITE_U32(p_ctx, length, "dwDuration"); + } + + AVI_END_CHUNK(p_ctx); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_stream_header_list(VC_CONTAINER_T *p_ctx, + unsigned int track_num, uint32_t list_size) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[track_num]; + VC_CONTAINER_STATUS_T status; + uint32_t chunk_size = 0; + + WRITE_FOURCC(p_ctx, VC_FOURCC('L','I','S','T'), "Chunk ID"); + WRITE_U32(p_ctx, list_size, "LIST Size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','l'), "Chunk ID"); + + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + + /* Write the stream header chunk ('strh') */ + status = avi_write_stream_header_chunk(p_ctx, track); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* Write the stream format chunk ('strf') */ + if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) + { + status = avi_write_stream_format_chunk(p_ctx, track, 0); + chunk_size = STREAM_POSITION(p_ctx) - 8; + } + vc_container_writer_extraio_disable(p_ctx, &module->null_io); + + status = avi_write_stream_format_chunk(p_ctx, track, chunk_size); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* If the track has DRM data, write it into the 'strd' chunk (we don't write + write codec configuration data into 'strd') */ + if (track->priv->drmdata && track->priv->drmdata_size) + { + WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','d'), "Chunk ID"); + WRITE_U32(p_ctx, track->priv->drmdata_size, "Chunk Size"); + WRITE_BYTES(p_ctx, track->priv->drmdata, track->priv->drmdata_size); + AVI_END_CHUNK(p_ctx); + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + } + + /* Write the super index chunk ('indx') */ + if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) + { + status = avi_write_super_index_chunk(p_ctx, track_num, 0); + chunk_size = STREAM_POSITION(p_ctx) - 8; + } + vc_container_writer_extraio_disable(p_ctx, &module->null_io); + + status = avi_write_super_index_chunk(p_ctx, track_num, chunk_size); + if (status != VC_CONTAINER_SUCCESS) return status; + + AVI_END_CHUNK(p_ctx); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_avi_header_chunk(VC_CONTAINER_T *p_ctx) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t bitrate = 0, width = 0, height = 0, frame_interval = 0; + uint32_t flags, num_chunks = 0, max_video_chunk_size = 0; + uint32_t num_streams = p_ctx->tracks_num; + unsigned int i; + + for (i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[i]; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[i]->priv->module; + if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + width = track->format->type->video.width; + height = track->format->type->video.height; + if (track->format->type->video.frame_rate_num) + frame_interval = track->format->type->video.frame_rate_den * UINT64_C(1000000) / + track->format->type->video.frame_rate_num; + num_chunks = track_module->chunk_index; + max_video_chunk_size = track_module->max_chunk_size; + break; + } + } + + flags = (module->index_offset && module->index_status == VC_CONTAINER_SUCCESS) ? + (AVIF_HASINDEX | AVIF_TRUSTCKTYPE) : 0; + + WRITE_FOURCC(p_ctx, VC_FOURCC('a','v','i','h'), "Chunk ID"); + WRITE_U32(p_ctx, 56, "Chunk Size"); + WRITE_U32(p_ctx, frame_interval, "dwMicroSecPerFrame"); + WRITE_U32(p_ctx, bitrate >> 3, "dwMaxBytesPerSec"); + WRITE_U32(p_ctx, 0, "dwPaddingGranularity"); + WRITE_U32(p_ctx, flags, "dwFlags"); + WRITE_U32(p_ctx, num_chunks, "dwTotalFrames"); + WRITE_U32(p_ctx, 0, "dwInitialFrames"); + WRITE_U32(p_ctx, num_streams, "dwStreams"); + WRITE_U32(p_ctx, max_video_chunk_size, "dwSuggestedBufferSize"); + WRITE_U32(p_ctx, width, "dwWidth"); + WRITE_U32(p_ctx, height, "dwHeight"); + WRITE_U32(p_ctx, 0, "dwReserved0"); + WRITE_U32(p_ctx, 0, "dwReserved1"); + WRITE_U32(p_ctx, 0, "dwReserved2"); + WRITE_U32(p_ctx, 0, "dwReserved3"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_header_list( VC_CONTAINER_T *p_ctx, uint32_t header_list_size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + unsigned int i; + + WRITE_FOURCC(p_ctx, VC_FOURCC('L','I','S','T'), "Chunk ID"); + WRITE_U32(p_ctx, header_list_size, "LIST Size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('h','d','r','l'), "Chunk ID"); + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + + /* Write the main AVI header chunk ('avih') */ + if ((status = avi_write_avi_header_chunk(p_ctx)) != VC_CONTAINER_SUCCESS) + return status; + + for (i = 0; i < p_ctx->tracks_num; i++) + { + uint32_t list_size = 0; + + /* Write a stream header list chunk ('strl') */ + if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) + { + status = avi_write_stream_header_list(p_ctx, i, 0); + if (status != VC_CONTAINER_SUCCESS) return status; + list_size = STREAM_POSITION(p_ctx) - 8; + } + vc_container_writer_extraio_disable(p_ctx, &module->null_io); + + status = avi_write_stream_header_list(p_ctx, i, list_size); + if (status != VC_CONTAINER_SUCCESS) return status; + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_headers( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint32_t header_list_offset, header_list_size = 0; + + /* Write the header list chunk ('hdrl') */ + if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) + { + status = avi_write_header_list(p_ctx, 0); + if (status != VC_CONTAINER_SUCCESS) return status; + header_list_size = STREAM_POSITION(p_ctx) - 8; + } + vc_container_writer_extraio_disable(p_ctx, &module->null_io); + + header_list_offset = STREAM_POSITION(p_ctx); + status = avi_write_header_list(p_ctx, header_list_size); + if (status == VC_CONTAINER_SUCCESS && !module->header_list_offset) + { + module->header_list_offset = header_list_offset; + module->header_list_size = header_list_size; + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_legacy_index_chunk( VC_CONTAINER_T *p_ctx, uint32_t index_size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint32_t chunk_offset = 4; + unsigned int track_num; + + vc_container_assert(8 + avi_num_chunks(p_ctx) * INT64_C(16) <= (int64_t)ULONG_MAX); + + if(module->null_io.refcount) + { + /* Assume that we're not actually writing the data, + just want know the index size */ + WRITE_BYTES(p_ctx, NULL, 8 + avi_num_chunks(p_ctx) * (int64_t)AVI_INDEX_ENTRY_SIZE); + return STREAM_STATUS(p_ctx); + } + + module->index_offset = STREAM_POSITION(p_ctx); + + WRITE_FOURCC(p_ctx, VC_FOURCC('i','d','x','1'), "Chunk ID"); + WRITE_U32(p_ctx, index_size, "Chunk Size"); + + /* Scan through all written entries, convert to appropriate index format */ + vc_container_io_seek(module->temp_io.io, INT64_C(0)); + + while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS) + { + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t chunk_size, flags; + + status = avi_read_index_entry(p_ctx, &track_num, &chunk_size); + if (status != VC_CONTAINER_SUCCESS) break; + + avi_chunk_id_from_track_num(p_ctx, &chunk_id, track_num); + flags = (chunk_size & AVI_INDEX_DELTAFRAME) ? 0 : AVIIF_KEYFRAME; + chunk_size &= ~AVI_INDEX_DELTAFRAME; + + WRITE_FOURCC(p_ctx, chunk_id, "Chunk ID"); + WRITE_U32(p_ctx, flags, "dwFlags"); + WRITE_U32(p_ctx, chunk_offset, "dwOffset"); + WRITE_U32(p_ctx, chunk_size, "dwSize"); + + chunk_offset += ((chunk_size + 1) & ~1) + 8; + } + + AVI_END_CHUNK(p_ctx); + + /* Note that currently, we might write a partial index but still set AVIF_HASINDEX */ + /* if ( STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS ) module->index_offset = 0 */ + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_standard_index_chunk( VC_CONTAINER_T *p_ctx, unsigned int index_track_num, + uint32_t index_size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[index_track_num]->priv->module; + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_FOURCC_T chunk_id; + int64_t base_offset = module->data_offset + 12; + uint32_t num_chunks = track_module->chunk_index; + uint32_t chunk_offset = 4; + + vc_container_assert(32 + num_chunks * (int64_t)AVI_STD_INDEX_ENTRY_SIZE <= (int64_t)ULONG_MAX); + + if(module->null_io.refcount) + { + /* Assume that we're not actually writing the data, just want know the index chunk size */ + WRITE_BYTES(p_ctx, NULL, 8 + 24 + num_chunks * INT64_C(8)); + return STREAM_STATUS(p_ctx); + } + + track_module->index_offset = STREAM_POSITION(p_ctx); + track_module->index_size = index_size ? (index_size - 8) : 0; + + avi_index_chunk_id_from_track_num(&chunk_id, index_track_num); + WRITE_FOURCC(p_ctx, chunk_id, "Chunk ID"); + WRITE_U32(p_ctx, index_size, "Chunk Size"); + + avi_chunk_id_from_track_num(p_ctx, &chunk_id, index_track_num); + WRITE_U16(p_ctx, 2, "wLongsPerEntry"); + WRITE_U8(p_ctx, 0, "bIndexSubType"); + WRITE_U8(p_ctx, AVI_INDEX_OF_CHUNKS, "bIndexType"); + WRITE_U32(p_ctx, num_chunks, "nEntriesInUse"); + WRITE_FOURCC(p_ctx, chunk_id, "dwChunkId"); + WRITE_U64(p_ctx, base_offset, "qwBaseOffset"); + WRITE_U32(p_ctx, 0, "dwReserved"); + + /* Scan through all written entries, convert to appropriate index format */ + vc_container_io_seek(module->temp_io.io, INT64_C(0)); + + while(STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS) + { + uint32_t chunk_size; + unsigned int track_num; + + status = avi_read_index_entry(p_ctx, &track_num, &chunk_size); + if (status != VC_CONTAINER_SUCCESS) break; + + if(track_num != index_track_num) continue; + + WRITE_U32(p_ctx, chunk_offset, "dwOffset"); + WRITE_U32(p_ctx, chunk_size, "dwSize"); + + chunk_offset += ((chunk_size + 1) & ~(1 | AVI_INDEX_DELTAFRAME)) + 12; + } + + AVI_END_CHUNK(p_ctx); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_legacy_index_data( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t chunk_size = 0; + + /* Write the legacy index chunk ('idx1') */ + if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) + { + status = avi_write_legacy_index_chunk(p_ctx, 0); + if (status != VC_CONTAINER_SUCCESS) return status; + chunk_size = STREAM_POSITION(p_ctx) - 8; + } + vc_container_writer_extraio_disable(p_ctx, &module->null_io); + + status = avi_write_legacy_index_chunk(p_ctx, chunk_size); + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_write_standard_index_data( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t chunk_size = 0; + unsigned int i; + + /* Write the standard index chunks ('ix00') */ + for (i = 0; i < p_ctx->tracks_num; i++) + { + if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) + { + status = avi_write_standard_index_chunk(p_ctx, i, 0); + if (status != VC_CONTAINER_SUCCESS) return status; + chunk_size = STREAM_POSITION(p_ctx) - 8; + } + vc_container_writer_extraio_disable(p_ctx, &module->null_io); + + status = avi_write_standard_index_chunk(p_ctx, i, chunk_size); + if (status != VC_CONTAINER_SUCCESS) return status; + } + + return status; +} + +/*****************************************************************************/ +static int64_t avi_calculate_file_size( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t filesize = 0; + int refcount; + + /* Start from current file position */ + filesize = STREAM_POSITION(p_ctx); + + refcount = vc_container_writer_extraio_enable(p_ctx, &module->null_io); + vc_container_assert(refcount == 0); /* Although perfectly harmless, we should + not be called with the null i/o enabled. */ + VC_CONTAINER_PARAM_UNUSED(refcount); + + do { + /* If we know what the final size of the chunk is going to be, + we can use that here to avoid writing a partial final packet */ + WRITE_BYTES(p_ctx, NULL, p_packet->frame_size ? p_packet->frame_size : p_packet->size); + AVI_END_CHUNK(p_ctx); + + /* Index entries for the chunk */ + WRITE_BYTES(p_ctx, NULL, AVI_INDEX_ENTRY_SIZE + AVI_STD_INDEX_ENTRY_SIZE); + + /* Current standard index data */ + if (avi_write_standard_index_data(p_ctx) != VC_CONTAINER_SUCCESS) break; + + /* Current legacy index data */ + status = avi_write_legacy_index_data(p_ctx); + if (status != VC_CONTAINER_SUCCESS) break; + } while(0); + + filesize += STREAM_POSITION(p_ctx); + + vc_container_writer_extraio_disable(p_ctx, &module->null_io); + + return filesize; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ + +static VC_CONTAINER_STATUS_T avi_writer_write( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_TRACK_T *track = NULL; + VC_CONTAINER_TRACK_MODULE_T *track_module = NULL; + + /* Check we have written headers before any data */ + if(!module->headers_written) + { + if ((status = avi_write_headers(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + module->headers_written = 1; + } + + /* Check that we have started the 'movi' list */ + if (!module->data_offset) + { + module->data_offset = STREAM_POSITION(p_ctx); + vc_container_assert(module->data_offset != INT64_C(0)); + + WRITE_FOURCC(p_ctx, VC_FOURCC('L','I','S','T'), "Chunk ID"); + WRITE_U32(p_ctx, 0, "LIST Size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('m','o','v','i'), "Chunk ID"); + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + } + + /* If the container is passing in a frame from a new track but we + arent't finished with a chunk from another track we need to finish + that chunk first */ + if (module->chunk_data_written && p_packet->track != module->current_track_num) + { + track_module = p_ctx->tracks[module->current_track_num]->priv->module; + status = avi_finish_data_chunk(p_ctx, module->chunk_data_written); + avi_write_index_entry(p_ctx, module->current_track_num, module->chunk_data_written, 0); + track_module->chunk_index++; + track_module->chunk_offs += module->chunk_data_written; + track_module->max_chunk_size = MAX(track_module->max_chunk_size, module->chunk_data_written); + module->chunk_data_written = 0; + if (status != VC_CONTAINER_SUCCESS) return status; + } + + /* Check we are not about to go over the limit of total number of chunks */ + if (avi_num_chunks(p_ctx) == (uint32_t)ULONG_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + if(STREAM_SEEKABLE(p_ctx)) + { + /* Check we are not about to go over the maximum file size */ + if (avi_calculate_file_size(p_ctx, p_packet) >= (int64_t)ULONG_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + } + + /* FIXME: are we expected to handle this case or should it be picked up by the above layer? */ + vc_container_assert(!(module->chunk_data_written && (p_packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_START))); + + track = p_ctx->tracks[p_packet->track]; + track_module = p_ctx->tracks[p_packet->track]->priv->module; + module->current_track_num = p_packet->track; + + if (module->chunk_data_written == 0) + { + /* This is the first fragment of the chunk */ + VC_CONTAINER_FOURCC_T chunk_id; + uint32_t chunk_size; + + avi_chunk_id_from_track_num(p_ctx, &chunk_id, p_packet->track); + + if (p_packet->frame_size) + { + /* We know what the final size of the chunk is going to be */ + chunk_size = module->chunk_size = p_packet->frame_size; + } + else + { + chunk_size = p_packet->size; + module->chunk_size = 0; + } + + WRITE_FOURCC(p_ctx, chunk_id, "Chunk ID"); + if(STREAM_SEEKABLE(p_ctx) || p_packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_END) + { + /* If the output stream can seek we can fix up the frame size later, and if the + * packet holds the whole frame we won't need to, so write data straight out. */ + WRITE_U32(p_ctx, chunk_size, "Chunk Size"); + WRITE_BYTES(p_ctx, p_packet->data, p_packet->size); + } + else + { + vc_container_assert(module->avi_frame_buffer); + if(p_packet->size > AVI_FRAME_BUFFER_SIZE) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + module->frame_packet = *p_packet; + module->frame_packet.data = module->avi_frame_buffer; + memcpy(module->frame_packet.data, + p_packet->data, module->frame_packet.size); + } + + module->chunk_data_written = p_packet->size; + } + else + { + if(module->frame_packet.size > 0 && module->avi_frame_buffer) + { + if(module->frame_packet.size > 0) + { + if(module->frame_packet.size + p_packet->size > AVI_FRAME_BUFFER_SIZE) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + memcpy(module->frame_packet.data + module->frame_packet.size, + p_packet->data, p_packet->size); + module->frame_packet.size += p_packet->size; + } + } + else + { + WRITE_BYTES(p_ctx, p_packet->data, p_packet->size); + } + module->chunk_data_written += p_packet->size; + } + + if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) + return status; + + if ((p_packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_END) || + (track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO && + track->format->type->audio.block_align && + module->chunk_data_written > AVI_AUDIO_CHUNK_SIZE_LIMIT)) + { + if(module->frame_packet.size > 0) + { + WRITE_U32(p_ctx, module->frame_packet.size, "Chunk Size"); + WRITE_BYTES(p_ctx, module->frame_packet.data, module->frame_packet.size); + p_packet->size = module->frame_packet.size; + module->frame_packet.size = 0; + } + + if (!module->chunk_size && module->chunk_data_written > p_packet->size) + { + /* The chunk size needs to be rewritten */ + status = avi_finish_data_chunk(p_ctx, module->chunk_data_written); + } + else + { + status = avi_finish_data_chunk(p_ctx, 0); + } + + if(!STREAM_SEEKABLE(p_ctx)) + { + /* If we are streaming then flush to avoid delaying data transport. */ + vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_FLUSH); + } + + if(STREAM_SEEKABLE(p_ctx)) + { + /* Keep track of data written so we can check we don't exceed file size and also for doing + * index fix-ups, but only do this if we are writing to a seekable IO. */ + avi_write_index_entry(p_ctx, p_packet->track, module->chunk_data_written, AVI_PACKET_IS_KEYFRAME(p_packet->flags)); + } + track_module->chunk_index++; + track_module->chunk_offs += module->chunk_data_written; + track_module->max_chunk_size = MAX(track_module->max_chunk_size, module->chunk_data_written); + module->chunk_data_written = 0; + + if (status != VC_CONTAINER_SUCCESS) return status; + } + + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_writer_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + + /* If we arent't finished with a chunk we need to finish it first */ + if (module->chunk_data_written) + { + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track_num]->priv->module; + status = avi_finish_data_chunk(p_ctx, module->chunk_data_written); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "warning, writing failed, last chunk truncated"); + } + avi_write_index_entry(p_ctx, module->current_track_num, module->chunk_data_written, 0); + track_module->chunk_index++; + track_module->chunk_offs += module->chunk_data_written; + track_module->max_chunk_size = MAX(track_module->max_chunk_size, module->chunk_data_written); + module->chunk_data_written = 0; + } + + if(STREAM_SEEKABLE(p_ctx)) + { + uint32_t filesize; + + /* Write standard index data before finalising the size of the 'movi' list */ + status = avi_write_standard_index_data(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + { + module->index_status = status; + LOG_DEBUG(p_ctx, "warning, writing standard index data failed, file will be malformed"); + } + + /* FIXME: support for multiple RIFF chunks (AVIX) */ + module->data_size = STREAM_POSITION(p_ctx) - module->data_offset - 8; + + /* Now write the legacy index */ + status = avi_write_legacy_index_data(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + { + module->index_status = status; + LOG_DEBUG(p_ctx, "warning, writing legacy index data failed, file will be malformed"); + } + + /* If we can, do the necessary fixups for values not know at the + time of writing chunk headers */ + + /* Rewrite the AVI RIFF chunk size */ + filesize = (uint32_t)STREAM_POSITION(p_ctx); + SEEK(p_ctx, 4); + WRITE_U32(p_ctx, filesize, "fileSize"); + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "warning, rewriting 'fileSize' failed, file will be malformed"); + } + + /* Rewrite the header list chunk ('hdrl') */ + SEEK(p_ctx, module->header_list_offset); + status = avi_write_header_list(p_ctx, module->header_list_size); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "warning, rewriting 'hdrl' failed, file will be malformed"); + } + + /* Rewrite the AVI RIFF 'movi' list size */ + SEEK(p_ctx, module->data_offset + 4); + WRITE_U32(p_ctx, module->data_size, "Chunk Size"); + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "warning, rewriting 'movi' list size failed, file will be malformed"); + } + } + + vc_container_writer_extraio_delete(p_ctx, &module->null_io); + if(module->temp_io.io) vc_container_writer_extraio_delete(p_ctx, &module->temp_io); + + for(i = 0; i < p_ctx->tracks_num; i++) + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + p_ctx->tracks_num = 0; + p_ctx->tracks = NULL; + + if(module->avi_frame_buffer) free(module->avi_frame_buffer); + free(module); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_writer_add_track( VC_CONTAINER_T *p_ctx, VC_CONTAINER_ES_FORMAT_T *format ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_TRACK_T *track = NULL; + + if (module->headers_written) return VC_CONTAINER_ERROR_FAILED; + + /* FIXME: should we check the format in more detail? */ + if((format->es_type != VC_CONTAINER_ES_TYPE_VIDEO && format->es_type != VC_CONTAINER_ES_TYPE_AUDIO) || + format->codec == VC_CONTAINER_CODEC_UNKNOWN) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + if(!(format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED)) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + /* Allocate new track */ + if(p_ctx->tracks_num >= AVI_TRACKS_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + p_ctx->tracks[p_ctx->tracks_num] = track = + vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + if(format->extradata_size) + { + status = vc_container_track_allocate_extradata( p_ctx, track, format->extradata_size ); + if(status) goto error; + } + + status = vc_container_format_copy(track->format, format, format->extradata_size); + if(status) goto error; + + p_ctx->tracks_num++; + return VC_CONTAINER_SUCCESS; + +error: + vc_container_free_track(p_ctx, track); + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avi_writer_control( VC_CONTAINER_T *p_ctx, VC_CONTAINER_CONTROL_T operation, va_list args ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + + switch(operation) + { + case VC_CONTAINER_CONTROL_TRACK_ADD: + { + VC_CONTAINER_ES_FORMAT_T *format = + (VC_CONTAINER_ES_FORMAT_T *)va_arg( args, VC_CONTAINER_ES_FORMAT_T * ); + return avi_writer_add_track(p_ctx, format); + } + case VC_CONTAINER_CONTROL_TRACK_ADD_DONE: + { + if(!module->headers_written) + { + if ((status = avi_write_headers(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + module->headers_written = 1; + return VC_CONTAINER_SUCCESS; + } + else + return VC_CONTAINER_ERROR_FAILED; + } + default: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + } +} + +/****************************************************************************** +Global function definitions. +******************************************************************************/ +VC_CONTAINER_STATUS_T avi_writer_open( VC_CONTAINER_T *p_ctx ) +{ + const char *extension = vc_uri_path_extension(p_ctx->priv->uri); + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = 0; + + /* Check if the user has specified a container */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + /* Check we're the right writer for this */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(strcasecmp(extension, "avi") && strcasecmp(extension, "divx")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + + /* Create a null i/o writer to help us out in writing our data */ + status = vc_container_writer_extraio_create_null(p_ctx, &module->null_io); + if(status != VC_CONTAINER_SUCCESS) goto error; + + if(STREAM_SEEKABLE(p_ctx)) + { + /* Create a temporary i/o writer for storage of index data while we are writing */ + status = vc_container_writer_extraio_create_temp(p_ctx, &module->temp_io); + if(status != VC_CONTAINER_SUCCESS) goto error; + } + else + { + module->avi_frame_buffer = malloc(AVI_FRAME_BUFFER_SIZE); + if(!module->avi_frame_buffer) + { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + } + module->frame_packet.size = 0; + + p_ctx->tracks = module->tracks; + + /* Write the RIFF chunk descriptor */ + WRITE_FOURCC(p_ctx, VC_FOURCC('R','I','F','F'), "RIFF ID"); + WRITE_U32(p_ctx, 0, "fileSize"); + WRITE_FOURCC(p_ctx, VC_FOURCC('A','V','I',' '), "fileType"); + + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) goto error; + + p_ctx->priv->pf_close = avi_writer_close; + p_ctx->priv->pf_write = avi_writer_write; + p_ctx->priv->pf_control = avi_writer_control; + + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "error opening stream"); + p_ctx->tracks_num = 0; + p_ctx->tracks = NULL; + if(module) + { + if(module->avi_frame_buffer) free(module->avi_frame_buffer); + free(module); + } + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak writer_open avi_writer_open +#endif diff --git a/containers/binary/CMakeLists.txt b/containers/binary/CMakeLists.txt new file mode 100644 index 0000000..c159b40 --- /dev/null +++ b/containers/binary/CMakeLists.txt @@ -0,0 +1,19 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_binary ${LIBRARY_TYPE} binary_reader.c) + +target_link_libraries(reader_binary containers) + +install(TARGETS reader_binary DESTINATION ${VMCS_PLUGIN_DIR}) + +add_library(writer_binary ${LIBRARY_TYPE} binary_writer.c) + +target_link_libraries(writer_binary containers) + +install(TARGETS writer_binary DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/binary/binary_reader.c b/containers/binary/binary_reader.c new file mode 100644 index 0000000..0bcf389 --- /dev/null +++ b/containers/binary/binary_reader.c @@ -0,0 +1,267 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define DEFAULT_BLOCK_SIZE (1024*16) +/* Work-around for JPEG because our decoder expects that much at the start */ +#define DEFAULT_JPEG_BLOCK_SIZE (1024*80) + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + unsigned int default_block_size; + unsigned int block_size; + bool init; + + VC_CONTAINER_STATUS_T status; + +} VC_CONTAINER_MODULE_T; + +static struct +{ + const char *ext; + VC_CONTAINER_ES_TYPE_T type; + VC_CONTAINER_FOURCC_T codec; + +} extension_to_format_table[] = +{ + /* Audio */ + {"mp3", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_MPGA}, + {"aac", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_MP4A}, + {"adts", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_MP4A}, + {"ac3", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_AC3}, + {"ec3", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_EAC3}, + {"amr", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_AMRNB}, + {"awb", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_AMRWB}, + {"evrc", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_EVRC}, + {"dts", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_DTS}, + + /* Video */ + {"m1v", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_MP1V}, + {"m2v", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_MP2V}, + {"m4v", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_MP4V}, + {"mp4v", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_MP4V}, + {"h263", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_H263}, + {"263", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_H263}, + {"h264", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_H264}, + {"264", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_H264}, + {"mvc", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_MVC}, + {"vc1l", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_WVC1}, + + /* Image */ + {"gif", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_GIF}, + {"jpg", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_JPEG}, + {"jpeg", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_JPEG}, + {"png", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_PNG}, + {"ppm", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_PPM}, + {"tga", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_TGA}, + {"bmp", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_BMP}, + + {"bin", 0, 0}, + {0, 0, 0} +}; + +static struct +{ + const char *ext; + VC_CONTAINER_ES_TYPE_T type; + VC_CONTAINER_FOURCC_T codec; + +} bin_extension_to_format_table[] = +{ + {"m4v.bin", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_MP4V}, + {"263.bin", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_H263}, + {"264.bin", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_H264}, + {0, 0, 0} +}; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T binary_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T binary_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int size; + + if(module->status != VC_CONTAINER_SUCCESS) + return module->status; + + if(!module->block_size) + { + module->block_size = module->default_block_size; + module->init = 0; + } + + packet->size = module->block_size; + packet->dts = packet->pts = VC_CONTAINER_TIME_UNKNOWN; + if(module->init) packet->dts = packet->pts = 0; + packet->track = 0; + packet->flags = 0; + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + { + size = SKIP_BYTES(p_ctx, module->block_size); + module->block_size -= size; + module->status = STREAM_STATUS(p_ctx); + return module->status; + } + + if(flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + size = MIN(module->block_size, packet->buffer_size); + size = READ_BYTES(p_ctx, packet->data, size); + module->block_size -= size; + packet->size = size; + + module->status = size ? VC_CONTAINER_SUCCESS : STREAM_STATUS(p_ctx); + return module->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T binary_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_PARAM_UNUSED(module); + VC_CONTAINER_PARAM_UNUSED(offset); + VC_CONTAINER_PARAM_UNUSED(mode); + VC_CONTAINER_PARAM_UNUSED(flags); + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T binary_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + for(; p_ctx->tracks_num > 0; p_ctx->tracks_num--) + vc_container_free_track(p_ctx, p_ctx->tracks[p_ctx->tracks_num-1]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T binary_reader_open( VC_CONTAINER_T *p_ctx ) +{ + const char *extension = vc_uri_path_extension(p_ctx->priv->uri); + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + VC_CONTAINER_ES_TYPE_T es_type = 0; + VC_CONTAINER_FOURCC_T codec = 0; + unsigned int i; + + /* Check if the user has specified a container */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + /* Check if the extension is supported */ + if(!extension || !vc_uri_path(p_ctx->priv->uri)) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + for(i = 0; extension_to_format_table[i].ext; i++) + { + if(strcasecmp(extension, extension_to_format_table[i].ext)) + continue; + + es_type = extension_to_format_table[i].type; + codec = extension_to_format_table[i].codec; + break; + } + if(!extension_to_format_table[i].ext) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* If this a .bin file we look in our bin list */ + for(i = 0; !codec && bin_extension_to_format_table[i].ext; i++) + { + if(!strstr(vc_uri_path(p_ctx->priv->uri), bin_extension_to_format_table[i].ext) && + !strstr(extension, bin_extension_to_format_table[i].ext)) + continue; + + es_type = bin_extension_to_format_table[i].type; + codec = bin_extension_to_format_table[i].codec; + break; + } + if(!codec) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks_num = 1; + p_ctx->tracks = &module->track; + p_ctx->tracks[0] = vc_container_allocate_track(p_ctx, 0); + if(!p_ctx->tracks[0]) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + p_ctx->tracks[0]->format->es_type = es_type; + p_ctx->tracks[0]->format->codec = codec; + p_ctx->tracks[0]->is_enabled = true; + module->default_block_size = DEFAULT_BLOCK_SIZE; + if(codec == VC_CONTAINER_CODEC_JPEG) + module->default_block_size = DEFAULT_JPEG_BLOCK_SIZE; + module->block_size = module->default_block_size; + module->init = 1; + + /* + * We now have all the information we really need to start playing the stream + */ + + p_ctx->priv->pf_close = binary_reader_close; + p_ctx->priv->pf_read = binary_reader_read; + p_ctx->priv->pf_seek = binary_reader_seek; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "binary: error opening stream (%i)", status); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open binary_reader_open +#endif diff --git a/containers/binary/binary_writer.c b/containers/binary/binary_writer.c new file mode 100644 index 0000000..b4580ea --- /dev/null +++ b/containers/binary/binary_writer.c @@ -0,0 +1,160 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Supported extensions +******************************************************************************/ +static const char *extensions[] = +{ "mp3", "aac", "adts", "ac3", "ec3", "amr", "awb", "evrc", "dts", + "m1v", "m2v", "mp4v", "h263", "263", "h264", "264", "mvc", + "bin", 0 +}; + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T binary_writer_open( VC_CONTAINER_T * ); + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T binary_writer_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + for(; p_ctx->tracks_num > 0; p_ctx->tracks_num--) + vc_container_free_track(p_ctx, p_ctx->tracks[p_ctx->tracks_num-1]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T binary_writer_write( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet ) +{ + WRITE_BYTES(p_ctx, packet->data, packet->size); + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T binary_writer_control( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_CONTROL_T operation, va_list args ) +{ + VC_CONTAINER_ES_FORMAT_T *format; + VC_CONTAINER_TRACK_T *track; + VC_CONTAINER_STATUS_T status; + + switch(operation) + { + case VC_CONTAINER_CONTROL_TRACK_ADD: + format = (VC_CONTAINER_ES_FORMAT_T *)va_arg( args, VC_CONTAINER_ES_FORMAT_T * ); + + /* Allocate and initialise track data */ + if(p_ctx->tracks_num >= 1) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + p_ctx->tracks[p_ctx->tracks_num] = track = vc_container_allocate_track(p_ctx, 0); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + if(format->extradata_size) + { + status = vc_container_track_allocate_extradata( p_ctx, track, format->extradata_size ); + if(status != VC_CONTAINER_SUCCESS) + { + vc_container_free_track(p_ctx, track); + return status; + } + WRITE_BYTES(p_ctx, format->extradata, format->extradata_size); + } + + vc_container_format_copy(track->format, format, format->extradata_size); + p_ctx->tracks_num++; + return VC_CONTAINER_SUCCESS; + + case VC_CONTAINER_CONTROL_TRACK_ADD_DONE: + return VC_CONTAINER_SUCCESS; + + default: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + } +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T binary_writer_open( VC_CONTAINER_T *p_ctx ) +{ + const char *extension = vc_uri_path_extension(p_ctx->priv->uri); + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + unsigned int i; + + /* Check if the user has specified a container */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + /* Check we're the right writer for this */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + for(i = 0; extensions[i]; i++) + if(!strcasecmp(extension, extensions[i])) break; + if(!extensions[i]) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = &module->track; + + p_ctx->priv->pf_close = binary_writer_close; + p_ctx->priv->pf_write = binary_writer_write; + p_ctx->priv->pf_control = binary_writer_control; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "binary: error opening stream (%i)", status); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak writer_open binary_writer_open +#endif diff --git a/containers/containers.h b/containers/containers.h new file mode 100644 index 0000000..0328aba --- /dev/null +++ b/containers/containers.h @@ -0,0 +1,746 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_H +#define VC_CONTAINERS_H + +/** \file containers.h + * Public API for container readers and writers + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "containers/containers_types.h" + +/** \defgroup VcContainerApi Container API + * API for container readers and writers */ +/* @{ */ + +/** Status codes returned by the container API */ +typedef enum +{ + VC_CONTAINER_SUCCESS = 0, /**< No error */ + VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED, /**< Format of container is not supported */ + VC_CONTAINER_ERROR_FORMAT_FEATURE_NOT_SUPPORTED, /**< Format of container uses unsupported features */ + VC_CONTAINER_ERROR_FORMAT_INVALID, /**< Format of container is invalid */ + VC_CONTAINER_ERROR_CORRUPTED, /**< Container is corrupted */ + VC_CONTAINER_ERROR_URI_NOT_FOUND, /**< URI could not be found */ + VC_CONTAINER_ERROR_URI_OPEN_FAILED, /**< URI could not be opened */ + VC_CONTAINER_ERROR_OUT_OF_MEMORY, /**< Out of memory */ + VC_CONTAINER_ERROR_OUT_OF_SPACE, /**< Out of disk space (used when writing) */ + VC_CONTAINER_ERROR_OUT_OF_RESOURCES, /**< Out of resources (other than memory) */ + VC_CONTAINER_ERROR_EOS, /**< End of stream reached */ + VC_CONTAINER_ERROR_LIMIT_REACHED, /**< User defined limit reached (used when writing) */ + VC_CONTAINER_ERROR_BUFFER_TOO_SMALL, /**< Given buffer is too small for data to be copied */ + VC_CONTAINER_ERROR_INCOMPLETE_DATA, /**< Requested data is incomplete */ + VC_CONTAINER_ERROR_NO_TRACK_AVAILABLE, /**< Container doesn't have any track */ + VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED, /**< Format of the track is not supported */ + VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION, /**< The requested operation is not supported */ + VC_CONTAINER_ERROR_INVALID_ARGUMENT, /**< The argument provided is invalid */ + VC_CONTAINER_ERROR_CONTINUE, /**< The requested operation was interrupted and needs to be tried again */ + VC_CONTAINER_ERROR_ABORTED, /**< The requested operation was aborted */ + VC_CONTAINER_ERROR_NOT_FOUND, /**< The requested data was not found */ + VC_CONTAINER_ERROR_DRM_NOT_AUTHORIZED, /**< The DRM was not authorized */ + VC_CONTAINER_ERROR_DRM_EXPIRED, /**< The DRM has expired */ + VC_CONTAINER_ERROR_DRM_FAILED, /**< Generic DRM error */ + VC_CONTAINER_ERROR_FAILED, /**< Generic error */ + VC_CONTAINER_ERROR_NOT_READY /**< The container was not yet able to carry out the operation. */ +} VC_CONTAINER_STATUS_T; + +/** Four Character Code type used to identify codecs, etc. */ +typedef uint32_t VC_CONTAINER_FOURCC_T; + +/** Type definition for language codes. + * Language are defined as ISO639 Alpha-3 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) */ +typedef uint8_t VC_CONTAINER_LANGUAGE_T[3]; + +/** Enumeration of the character encodings supported. */ +typedef enum { + VC_CONTAINER_CHAR_ENCODING_UNKNOWN = 0, /**< Encoding is unknown */ + VC_CONTAINER_CHAR_ENCODING_UTF8 /**< UTF8 encoding */ +} VC_CONTAINER_CHAR_ENCODING_T; + +/** \name Container Capabilities + * The following flags are exported by containers to describe their capabilities */ +/* @{ */ +/** Type definition for container capabilities */ +typedef uint32_t VC_CONTAINER_CAPABILITIES_T; +/** The container can seek */ +#define VC_CONTAINER_CAPS_CAN_SEEK 0x1 +/** Seeking is fast. The absence of this flag can be used as a hint to avoid seeking as much as possible */ +#define VC_CONTAINER_CAPS_SEEK_IS_FAST 0x2 +/** The container controls the pace at which it is reading the data */ +#define VC_CONTAINER_CAPS_CAN_CONTROL_PACE 0x4 +/** The container provides an index. This basically means that seeking will be precise and fast */ +#define VC_CONTAINER_CAPS_HAS_INDEX 0x8 +/** The container provides keyframe information */ +#define VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG 0x10 +/** The container supports adding tracks after TRACK_ADD_DONE control message has been sent */ +#define VC_CONTAINER_CAPS_DYNAMIC_TRACK_ADD 0x20 +/** The container supports forcing reading of a given track */ +#define VC_CONTAINER_CAPS_FORCE_TRACK 0x40 +/* @} */ + +/** \defgroup VcContainerMetadata Container Metadata + * Container metadata contains descriptive information which is associated with the multimedia data */ +/* @{ */ + +/** Enumeration of the different metadata keys available. */ +typedef enum { + /* Metadata of global scope */ + VC_CONTAINER_METADATA_KEY_TITLE = VC_FOURCC('t','i','t','l'), + VC_CONTAINER_METADATA_KEY_ARTIST = VC_FOURCC('a','r','t','i'), + VC_CONTAINER_METADATA_KEY_ALBUM = VC_FOURCC('a','l','b','m'), + VC_CONTAINER_METADATA_KEY_DESCRIPTION = VC_FOURCC('d','e','s','c'), + VC_CONTAINER_METADATA_KEY_YEAR = VC_FOURCC('y','e','a','r'), + VC_CONTAINER_METADATA_KEY_GENRE = VC_FOURCC('g','e','n','r'), + VC_CONTAINER_METADATA_KEY_TRACK = VC_FOURCC('t','r','a','k'), + VC_CONTAINER_METADATA_KEY_LYRICS = VC_FOURCC('l','y','r','x'), + + VC_CONTAINER_METADATA_KEY_UNKNOWN = 0 + +} VC_CONTAINER_METADATA_KEY_T; + +/** Definition of the metadata type. + * This type is used to store one element of metadata */ +typedef struct VC_CONTAINER_METADATA_T +{ + /** Identifier for the type of metadata the value refers to. + * Using an enum for the id will mean that a list of possible values will have to be + * defined and maintained. This might limit extensibility and customisation.\n + * Maybe it would be better to use a FOURCC or even a string here. */ + VC_CONTAINER_METADATA_KEY_T key; + + VC_CONTAINER_LANGUAGE_T language; /**< Language code for the metadata */ + VC_CONTAINER_CHAR_ENCODING_T encoding; /**< Encoding of the metadata */ + + /** Metadata value. This value is defined as null-terminated UTF-8 string.\n + * Do we want to support other types than strings (e.g. integer) ?\n + * We need an encoding conversion library! */ + char *value; + + /** Size of the memory area reserved for metadata value (including any + * terminating characters). */ + unsigned int size; +} VC_CONTAINER_METADATA_T; +/* @} */ + +/** \defgroup VcContainerESFormat Container Elementary Stream Format + * This describes the format of an elementary stream associated with a track */ +/* @{ */ + +/** Enumeration of the different types of elementary streams. + * This divides elementary streams into 4 big categories. */ +typedef enum +{ + VC_CONTAINER_ES_TYPE_UNKNOWN, /**< Unknown elementary stream type */ + VC_CONTAINER_ES_TYPE_AUDIO, /**< Audio elementary stream */ + VC_CONTAINER_ES_TYPE_VIDEO, /**< Video elementary stream */ + VC_CONTAINER_ES_TYPE_SUBPICTURE /**< Sub-picture elementary stream (e.g. subtitles, overlays) */ + +} VC_CONTAINER_ES_TYPE_T; + +/** Definition of a video format. + * This describes the properties specific to a video stream */ +typedef struct VC_CONTAINER_VIDEO_FORMAT_T +{ + uint32_t width; /**< Width of the frame */ + uint32_t height; /**< Height of the frame */ + uint32_t visible_width; /**< Width of the visible area of the frame */ + uint32_t visible_height; /**< Height of the visible area of the frame */ + uint32_t x_offset; /**< Offset to the start of the visible width */ + uint32_t y_offset; /**< Offset to the start of the visible height */ + uint32_t frame_rate_num; /**< Frame rate numerator */ + uint32_t frame_rate_den; /**< Frame rate denominator */ + uint32_t par_num; /**< Pixel aspect ratio numerator */ + uint32_t par_den; /**< Pixel aspect ratio denominator */ +} VC_CONTAINER_VIDEO_FORMAT_T; + +/** Enumeration for the different channel locations */ +typedef enum +{ + VC_CONTAINER_AUDIO_CHANNEL_LEFT = 0, /**< Left channel */ + VC_CONTAINER_AUDIO_CHANNEL_RIGHT, /**< Right channel */ + VC_CONTAINER_AUDIO_CHANNEL_CENTER, /**< Center channel */ + VC_CONTAINER_AUDIO_CHANNEL_LOW_FREQUENCY, /**< Low frequency channel */ + VC_CONTAINER_AUDIO_CHANNEL_BACK_LEFT, /**< Back left channel */ + VC_CONTAINER_AUDIO_CHANNEL_BACK_RIGHT, /**< Back right channel */ + VC_CONTAINER_AUDIO_CHANNEL_BACK_CENTER, /**< Back center channel */ + VC_CONTAINER_AUDIO_CHANNEL_SIDE_LEFT, /**< Side left channel */ + VC_CONTAINER_AUDIO_CHANNEL_SIDE_RIGHT, /**< Side right channel */ + + VC_CONTAINER_AUDIO_CHANNELS_MAX = 32 /**< Maximum number of channels supported */ + +} VC_CONTAINER_AUDIO_CHANNEL_T; + +/** \name Audio format flags + * \anchor audioformatflags + * The following flags describe properties of an audio stream */ +/* @{ */ +#define VC_CONTAINER_AUDIO_FORMAT_FLAG_CHANNEL_MAPPING 0x1 /**< Channel mapping available */ +/* @} */ + +/** Definition of an audio format. + * This describes the properties specific to an audio stream */ +typedef struct VC_CONTAINER_AUDIO_FORMAT_T +{ + uint32_t channels; /**< Number of audio channels */ + uint32_t sample_rate; /**< Sample rate */ + + uint32_t bits_per_sample; /**< Bits per sample */ + uint32_t block_align; /**< Size of a block of data */ + + uint32_t flags; /**< Flags describing the audio format. + * See \ref audioformatflags "Audio format flags". */ + + /** Mapping of the channels in order of appearance */ + VC_CONTAINER_AUDIO_CHANNEL_T channel_mapping[VC_CONTAINER_AUDIO_CHANNELS_MAX]; + + uint16_t gap_delay; /**< Delay introduced by the encoder. Used for gapless playback */ + uint16_t gap_padding; /**< Padding introduced by the encoder. Used for gapless playback */ + + /** Replay gain information. First element is the track information and the second + * is the album information. */ + struct { + float peak; /**< Peak value (full range is 1.0) */ + float gain; /**< Gain value in dB */ + } replay_gain[2]; + +} VC_CONTAINER_AUDIO_FORMAT_T; + +/** Definition of a subpicture format. + * This describes the properties specific to a subpicture stream */ +typedef struct VC_CONTAINER_SUBPICTURE_FORMAT_T +{ + VC_CONTAINER_CHAR_ENCODING_T encoding; /**< Encoding for text based subpicture formats */ + uint32_t x_offset; /**< Width offset to the start of the subpicture */ + uint32_t y_offset; /**< Height offset to the start of the subpicture */ +} VC_CONTAINER_SUBPICTURE_FORMAT_T; + +/** \name Elementary stream format flags + * \anchor esformatflags + * The following flags describe properties of an elementary stream */ +/* @{ */ +#define VC_CONTAINER_ES_FORMAT_FLAG_FRAMED 0x1 /**< Elementary stream is framed */ +/* @} */ + +/** Definition of the type specific format. + * This describes the type specific information of the elementary stream. */ +typedef union +{ + VC_CONTAINER_AUDIO_FORMAT_T audio; /**< Audio specific information */ + VC_CONTAINER_VIDEO_FORMAT_T video; /**< Video specific information */ + VC_CONTAINER_SUBPICTURE_FORMAT_T subpicture; /**< Subpicture specific information */ +} VC_CONTAINER_ES_SPECIFIC_FORMAT_T; + +/** Definition of an elementary stream format */ +typedef struct VC_CONTAINER_ES_FORMAT_T +{ + VC_CONTAINER_ES_TYPE_T es_type; /**< Type of the elementary stream */ + VC_CONTAINER_FOURCC_T codec; /**< Coding of the elementary stream */ + VC_CONTAINER_FOURCC_T codec_variant; /**< If set, indicates a variant of the coding */ + + VC_CONTAINER_ES_SPECIFIC_FORMAT_T *type; /**< Type specific information for the elementary stream */ + + uint32_t bitrate; /**< Bitrate */ + + VC_CONTAINER_LANGUAGE_T language; /**< Language code for the elementary stream */ + uint32_t group_id; /**< ID of the group this elementary stream belongs to */ + + uint32_t flags; /**< Flags describing the properties of an elementary stream. + * See \ref esformatflags "Elementary stream format flags". */ + + unsigned int extradata_size; /**< Size of the codec specific data */ + uint8_t *extradata; /**< Codec specific data */ + +} VC_CONTAINER_ES_FORMAT_T; +/* @} */ + +/** \defgroup VcContainerPacket Container Packet + * A container packet is the unit of data that is being read from or written to a container */ +/* @{ */ + +/** Structure describing a data packet */ +typedef struct VC_CONTAINER_PACKET_T +{ + struct VC_CONTAINER_PACKET_T *next; /**< Used to build lists of packets */ + uint8_t *data; /**< Pointer to the buffer containing the actual data for the packet */ + unsigned int buffer_size; /**< Size of the p_data buffer. This is used to indicate how much data can be read in p_data */ + unsigned int size; /**< Size of the data contained in p_data */ + unsigned int frame_size; /**< If set, indicates the size of the frame this packet belongs to */ + int64_t pts; /**< Presentation Timestamp of the packet */ + int64_t dts; /**< Decoding Timestamp of the packet */ + uint64_t num; /**< Number of this packet */ + uint32_t track; /**< Track associated with this packet */ + uint32_t flags; /**< Flags associated with this packet */ + + void *user_data; /**< Field reserved for use by the client */ + void *framework_data; /**< Field reserved for use by the framework */ + +} VC_CONTAINER_PACKET_T; + +/** \name Container Packet Flags + * The following flags describe properties of the data packet */ +/* @{ */ +#define VC_CONTAINER_PACKET_FLAG_KEYFRAME 0x01 /**< Packet is a keyframe */ +#define VC_CONTAINER_PACKET_FLAG_FRAME_START 0x02 /**< Packet starts a frame */ +#define VC_CONTAINER_PACKET_FLAG_FRAME_END 0x04 /**< Packet ends a frame */ +#define VC_CONTAINER_PACKET_FLAG_FRAME 0x06 /**< Packet contains only complete frames */ +#define VC_CONTAINER_PACKET_FLAG_DISCONTINUITY 0x08 /**< Packet comes after a discontinuity in the stream. Decoders might have to be flushed */ +#define VC_CONTAINER_PACKET_FLAG_ENCRYPTED 0x10 /**< Packet contains DRM encrypted data */ +#define VC_CONTAINER_PACKET_FLAG_CONFIG 0x20 /**< Packet contains stream specific config data */ +/* @} */ + +/** \name Special Unknown Time Value + * This is the special value used to signal that a timestamp is not known */ +/* @{ */ +#define VC_CONTAINER_TIME_UNKNOWN (INT64_C(1)<<63) /**< Special value for signalling that time is not known */ +/* @} */ + +/* @} */ + +/** \name Track flags + * \anchor trackflags + * The following flags describe properties of a track */ +/* @{ */ +#define VC_CONTAINER_TRACK_FLAG_CHANGED 0x1 /**< Track definition has changed */ +/* @} */ + +/** Definition of the track type */ +typedef struct VC_CONTAINER_TRACK_T +{ + struct VC_CONTAINER_TRACK_PRIVATE_T *priv; /**< Private member used by the implementation */ + uint32_t is_enabled; /**< Flag to specify if the track is enabled */ + uint32_t flags; /**< Flags describing the properties of a track. + * See \ref trackflags "Track flags". */ + + VC_CONTAINER_ES_FORMAT_T *format; /**< Format of the elementary stream contained in the track */ + + unsigned int meta_num; /**< Number of metadata elements associated with the track */ + VC_CONTAINER_METADATA_T **meta; /**< Array of metadata elements associated with the track */ + +} VC_CONTAINER_TRACK_T; + +/** Definition of the DRM type */ +typedef struct VC_CONTAINER_DRM_T +{ + VC_CONTAINER_FOURCC_T format; /**< Four character code describing the format of the DRM in use */ + unsigned int views_max; /**< Maximum number of views allowed */ + unsigned int views_current; /**< Current number of views */ + +} VC_CONTAINER_DRM_T; + +/** Type definition for the progress reporting function. This function will be called regularly + * by the container during a call which blocks for too long and will report the progress of the + * operation as an estimated total length in microseconds and a percentage done. + * Returning anything else than VC_CONTAINER_SUCCESS in this function will abort the current + * operation. */ +typedef VC_CONTAINER_STATUS_T (*VC_CONTAINER_PROGRESS_REPORT_FUNC_T)(void *userdata, + int64_t length, unsigned int percentage_done); + +/** \name Container Events + * The following flags are exported by containers to notify the application of events */ +/* @{ */ +/** Type definition for container events */ +typedef uint32_t VC_CONTAINER_EVENTS_T; +#define VC_CONTAINER_EVENT_TRACKS_CHANGE 1 /**< Track information has changed */ +#define VC_CONTAINER_EVENT_METADATA_CHANGE 2 /**< Metadata has changed */ +/* @} */ + +/** Definition of the container context */ +typedef struct VC_CONTAINER_T +{ + struct VC_CONTAINER_PRIVATE_T *priv; /**< Private member used by the implementation */ + + VC_CONTAINER_EVENTS_T events; /**< Events generated by the container */ + VC_CONTAINER_CAPABILITIES_T capabilities; /**< Capabilities exported by the container */ + + VC_CONTAINER_PROGRESS_REPORT_FUNC_T pf_progress; /**< Progress report function pointer */ + void *progress_userdata; /**< Progress report user data */ + + int64_t duration; /**< Duration of the media in microseconds */ + int64_t position; /**< Current time position into the media */ + int64_t size; /**< Size of the media in bytes */ + + unsigned int tracks_num; /**< Number of tracks available */ + /** Pointer to an array of pointers to track elements. + * The reasoning for using a pointer to pointers here is to allow us to extend + * VC_CONTAINER_TRACK_T without loosing binary backward compatibility. */ + VC_CONTAINER_TRACK_T **tracks; + + unsigned int meta_num; /**< Number of metadata elements associated with the container */ + VC_CONTAINER_METADATA_T **meta; /**< Array of metadata elements associated with the container */ + + VC_CONTAINER_DRM_T *drm; /**< Description used for DRM protected content */ + +} VC_CONTAINER_T; + +/** Forward declaration of a container input / output context. + * This structure defines the context for a container io instance */ +typedef struct VC_CONTAINER_IO_T VC_CONTAINER_IO_T; + +/** Opens the media container pointed to by the URI for reading. + * This will create an an instance of a container reader and its associated context. + * The context returned will also be filled with the information retrieved from the media. + * + * If the media isn't accessible or recognized, this will return a null pointer as well as + * an error code indicating why this failed. + * + * \param psz_uri Unified Resource Identifier pointing to the media container + * \param status Returns the status of the operation + * \param pf_progress User provided function pointer to a progress report function. Can be set to + * null if no progress report is wanted. This function will be used during + * the whole lifetime of the instance (i.e. it will be used during + * open / seek / close) + * \param progress_userdata User provided pointer that will be passed during the progress report + * function call. + * \return A pointer to the context of the new instance of the + * container reader. Returns NULL on failure. + */ +VC_CONTAINER_T *vc_container_open_reader( const char *psz_uri, VC_CONTAINER_STATUS_T *status, + VC_CONTAINER_PROGRESS_REPORT_FUNC_T pf_progress, void *progress_userdata); + +/** Opens for reading the media container pointed to by the container i/o. + * This will create an an instance of a container reader and its associated context. + * The context returned will also be filled with the information retrieved from the media. + * + * If the media isn't accessible or recognized, this will return a null pointer as well as + * an error code indicating why this failed. + * + * \param p_io Instance of the container i/o to use + * \param psz_uri Unified Resource Identifier pointing to the media container (optional) + * \param status Returns the status of the operation + * \param pf_progress User provided function pointer to a progress report function. Can be set to + * null if no progress report is wanted. This function will be used during + * the whole lifetime of the instance (i.e. it will be used during + * open / seek / close) + * \param progress_userdata User provided pointer that will be passed during the progress report + * function call. + * \return A pointer to the context of the new instance of the + * container reader. Returns NULL on failure. + */ +VC_CONTAINER_T *vc_container_open_reader_with_io( VC_CONTAINER_IO_T *p_io, + const char *psz_uri, VC_CONTAINER_STATUS_T *status, + VC_CONTAINER_PROGRESS_REPORT_FUNC_T pf_progress, void *progress_userdata); + +/** Opens the media container pointed to by the URI for writing. + * This will create an an instance of a container writer and its associated context. + * The context returned will be initialised to sensible values. + * + * The application will need to add all the media tracks using \ref vc_container_control before + * it starts writing data using \ref vc_container_write. + * + * If the media isn't accessible or recognized, this will return a null pointer as well as + * an error code indicating why this failed. + * + * \param psz_uri Unified Resource Identifier pointing to the media container + * \param status Returns the status of the operation + * \param pf_progress User provided function pointer to a progess report function. Can be set to + * null if no progress report is wanted. + * \param progress_userdata User provided pointer that will be passed during the progress report + * function call. + * \return A pointer to the context of the new instance of the + * container writer. Returns NULL on failure. + */ +VC_CONTAINER_T *vc_container_open_writer( const char *psz_uri, VC_CONTAINER_STATUS_T *status, + VC_CONTAINER_PROGRESS_REPORT_FUNC_T pf_progress, void *progress_userdata); + +/** Closes an instance of a container reader / writer. + * This will free all the resources associated with the context. + * + * \param context Pointer to the context of the instance to close + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_close( VC_CONTAINER_T *context ); + +/** \name Container read flags + * The following flags can be passed during a read call */ +/* @{ */ +/** Type definition for the read flags */ +typedef uint32_t VC_CONTAINER_READ_FLAGS_T; +/** Ask the container to only return information on the next packet without reading it */ +#define VC_CONTAINER_READ_FLAG_INFO 1 +/** Ask the container to skip the next packet */ +#define VC_CONTAINER_READ_FLAG_SKIP 2 +/** Force the container to read data from the specified track */ +#define VC_CONTAINER_READ_FLAG_FORCE_TRACK 4 +/* @} */ + +/** Reads a data packet from a container reader. + * By default, the reader will read whatever packet comes next in the container and update the + * given \ref VC_CONTAINER_PACKET_T structure with this packet's information. + * This behaviour can be changed using the \ref VC_CONTAINER_READ_FLAGS_T.\n + * \ref VC_CONTAINER_READ_FLAG_INFO will instruct the reader to only return information on the + * following packet but not its actual data. The data can be retreived later by issuing another + * read request.\n + * \ref VC_CONTAINER_READ_FLAG_FORCE_TRACK will force the reader to read the next packet for the + * selected track (as present in the \ref VC_CONTAINER_PACKET_T structure) instead of defaulting + * to reading the packet which comes next in the container.\n + * \ref VC_CONTAINER_READ_FLAG_SKIP will instruct the reader to skip the next packet. In this case + * it isn't necessary for the caller to pass a pointer to a \ref VC_CONTAINER_PACKET_T structure + * unless the \ref VC_CONTAINER_READ_FLAG_INFO is also given.\n + * A combination of all these flags can be used. + * + * \param context Pointer to the context of the reader to use + * \param packet Pointer to the VC_CONTAINER_PACKET_T structure describing the data packet + * This needs to be partially filled before the call (buffer, buffer_size) + * \param flags Flags controlling the read operation + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_read( VC_CONTAINER_T *context, + VC_CONTAINER_PACKET_T *packet, VC_CONTAINER_READ_FLAGS_T flags ); + +/** Writes a data packet to a container writer. + * + * \param context Pointer to the context of the writer to use + * \param packet Pointer to the VC_CONTAINER_PACKET_T structure describing the data packet + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_write( VC_CONTAINER_T *context, + VC_CONTAINER_PACKET_T *packet ); + +/** Definition of the different seek modes */ +typedef enum +{ + /** The offset provided for seeking is an absolute time offset in microseconds */ + VC_CONTAINER_SEEK_MODE_TIME = 0, + /** The offset provided for seeking is a percentage (Q32 ?) */ + VC_CONTAINER_SEEK_MODE_PERCENT + +} VC_CONTAINER_SEEK_MODE_T; + +/** \name Container Seek Flags + * The following flags control seek operations */ +/* @{ */ +/** Type definition for the seek flags */ +typedef uint32_t VC_CONTAINER_SEEK_FLAGS_T; +/** Choose precise seeking even if slower */ +#define VC_CONTAINER_SEEK_FLAG_PRECISE 0x1 +/** By default a seek will always seek to the keyframe which comes just before the requested + * position. This flag allows the caller to force the container to seek to the keyframe which + * comes just after the requested position. */ +#define VC_CONTAINER_SEEK_FLAG_FORWARD 0x2 +/* @} */ + +/** Seek into a container reader. + * + * \param context Pointer to the context of the reader to use + * \param offset Offset to seek to. Used as an input as well as output value. + * \param mode Seeking mode requested. + * \param flags Flags affecting the seeking operation. + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_seek( VC_CONTAINER_T *context, int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags); + +/** Performance statistics. + */ +/** The maximum number of bins a statistics value is held in */ +#define VC_CONTAINER_STATS_BINS 10 + +/** This type is used to represent multiple values of a statistic. + */ +typedef struct VC_CONTAINER_STATS_T +{ + /** The number of places to right shift count before using. Resulting values + * of zero are rounded to 1. */ + uint32_t shift; + + /** We store VC_CONTAINER_STATS_BINS+1 records, in sorted order of numpc. + * At least one will be invalid and all zero. We combine adjacent records + * as necessary. */ + struct { + /** Sum of count. For single value statistics this is the freqency, for paired statistics + * this is the number of bytes written. */ + uint32_t count; + /** Number of count. For single value statistics this is the total value, for paired statistics + * this is the total length of time. */ + uint32_t num; + /** Number>>shift per count. Stored to save recalculation. */ + uint32_t numpc; + } record[VC_CONTAINER_STATS_BINS+1]; +} VC_CONTAINER_STATS_T; + +/** This type represents the statistics saved by the io layer. */ +typedef struct VC_CONTAINER_WRITE_STATS_T +{ + /** This logs the number of bytes written in count, and the microseconds taken to write + * in num. */ + VC_CONTAINER_STATS_T write; + /** This logs the length of time the write function has to wait for the asynchronous task. */ + VC_CONTAINER_STATS_T wait; + /** This logs the length of time that we wait for a flush command to complete. */ + VC_CONTAINER_STATS_T flush; +} VC_CONTAINER_WRITE_STATS_T; + + +/** Control operations which can be done on containers. */ +typedef enum +{ + /** Adds a new track to the list of tracks. This should be used by writers to create + * their list of tracks.\n + * Arguments:\n + * arg1= VC_CONTAINER_ES_FORMAT_T *: format of the track to add\n + * return= VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED if the format is not supported */ + VC_CONTAINER_CONTROL_TRACK_ADD = 0, + + /** Specifies that we're done adding new tracks. This is optional but can be used by writers + * to trigger the writing of the container header early. If this isn't used, the header will be + * written when the first data packet is received.\n + * No arguments.\n + * return= VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED if the format is not supported */ + VC_CONTAINER_CONTROL_TRACK_ADD_DONE, + + /** Change the format of a track in the list of tracks. This should be used by writers to modify + * the format of a track at run-time.\n + * Arguments:\n + * arg1= unsigned int: index of track to change\n + * arg2= VC_CONTAINER_ES_FORMAT_T *: format of the track to add\n + * return= VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED if the format is not supported */ + VC_CONTAINER_CONTROL_TRACK_CHANGE, + + /** Deletes a track from the list of tracks. This should be used by writers to delete tracks + * during run-time. Note that vc_container_close will automatically delete all track so it + * isn't necessary to call this before closing a writer.\n + * Arguments:\n + * arg1= index of the track to delete */ + VC_CONTAINER_CONTROL_TRACK_DEL, + + /** Activate the playback of DRM protected content.\n + * No arguments.\n + * return= one of the VC_CONTAINER_ERROR_DRM error codes if content can't be played */ + VC_CONTAINER_CONTROL_DRM_PLAY, + + /** TBD */ + VC_CONTAINER_CONTROL_METADATA_ADD, + /** TBD */ + VC_CONTAINER_CONTROL_METADATA_CHANGE, + /** TBD */ + VC_CONTAINER_CONTROL_METADATA_DEL, + + /** TBD */ + VC_CONTAINER_CONTROL_CHAPTER_ADD, + /** TBD */ + VC_CONTAINER_CONTROL_CHAPTER_DEL, + + /** Set a maximum size for files generated by writers.\n + * Arguments:\n + * arg1= uint64_t: maximum size */ + VC_CONTAINER_CONTROL_SET_MAXIMUM_SIZE, + + /** Enables/disabled performance statistic gathering.\n + * Arguments:\n + * arg1= bool: enable or disable */ + VC_CONTAINER_CONTROL_SET_IO_PERF_STATS, + + /** Collects performance statistics.\n + * Arguments:\n + * arg1= VC_CONTAINER_WRITE_STATS_T *: */ + VC_CONTAINER_CONTROL_GET_IO_PERF_STATS, + + /** HACK.\n + * Arguments:\n + * arg1= void (*)(void *): callback function + * arg1= void *: opaque pointer to pass during the callback */ + VC_CONTAINER_CONTROL_SET_IO_BUFFER_FULL_CALLBACK, + + /** Set the I/O read buffer size to be used.\n + * Arguments:\n + * arg1= uint32_t: New buffer size in bytes*/ + VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE, + + /** Set the timeout on I/O read operations, if applicable.\n + * Arguments:\n + * arg1= uint32_t: New timeout in milliseconds, or VC_CONTAINER_READ_TIMEOUT_BLOCK */ + VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, + + /** Set the timestamp base.\n + * The timestamp passed equates to time zero for the stream.\n + * Arguments:\n + * arg1= uint32_t: Timestamp base in stream clock units. */ + VC_CONTAINER_CONTROL_SET_TIMESTAMP_BASE, + + /** Set the next expected sequence number for the stream.\n + * Arguments:\n + * arg1= uint32_t: Next expected sequence number. */ + VC_CONTAINER_CONTROL_SET_NEXT_SEQUENCE_NUMBER, + + /** Set the source ID for the container.\n + * Arguments:\n + * arg1= uint32_t: Source identifier. */ + VC_CONTAINER_CONTROL_SET_SOURCE_ID, + + /** Arguments:\n + * arg1= void *: metadata buffer + * arg2= unsigned long: length of metadata in bytes */ + VC_CONTAINER_CONTROL_GET_DRM_METADATA, + + /** Arguments:\n + * arg1= unsigned long: track number + * arg2= VC_CONTAINER_FOURCC_T : drm type + * arg3= void *: encryption configuration parameters. + * arg4= unsigned long: configuration data length */ + VC_CONTAINER_CONTROL_ENCRYPT_TRACK, + + /** Causes the io to be flushed.\n + * Arguments: none */ + VC_CONTAINER_CONTROL_IO_FLUSH, + + /** Request the container reader to packetize data for the specified track. + * Arguments:\n + * arg1= unsigned long: track number + * arg2= VC_CONTAINER_FOURCC_T: codec variant to output */ + VC_CONTAINER_CONTROL_TRACK_PACKETIZE, + + /** Private user extensions must be above this number */ + VC_CONTAINER_CONTROL_USER_EXTENSIONS = 0x1000 + +} VC_CONTAINER_CONTROL_T; + +/** Used with the VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS control to indicate the read shall + * block until either data is available, or an error occurs. + */ +#define VC_CONTAINER_READ_TIMEOUT_BLOCK (uint32_t)(-1) + +/** Extensible control function for container readers and writers. + * This function takes a variable number of arguments which will depend on the specific operation. + * + * \param context Pointer to the VC_CONTAINER_T context to use + * \param operation The requested operation + * \return the status of the operation. Can be \ref VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION + * if the operation is not supported or implemented by the container. + */ +VC_CONTAINER_STATUS_T vc_container_control( VC_CONTAINER_T *context, VC_CONTAINER_CONTROL_T operation, ... ); + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* VC_CONTAINERS_H */ diff --git a/containers/containers_codecs.h b/containers/containers_codecs.h new file mode 100644 index 0000000..3a6bc4b --- /dev/null +++ b/containers/containers_codecs.h @@ -0,0 +1,214 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_CODECS_H +#define VC_CONTAINERS_CODECS_H + +/** \file containers_codecs.h + * Codec helpers + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "containers/containers_types.h" + +/* Video */ +#define VC_CONTAINER_CODEC_MP1V VC_FOURCC('m','p','1','v') +#define VC_CONTAINER_CODEC_MP2V VC_FOURCC('m','p','2','v') +#define VC_CONTAINER_CODEC_MP4V VC_FOURCC('m','p','4','v') +#define VC_CONTAINER_CODEC_DIV3 VC_FOURCC('d','i','v','3') +#define VC_CONTAINER_CODEC_DIV4 VC_FOURCC('d','i','v','4') +#define VC_CONTAINER_CODEC_H263 VC_FOURCC('h','2','6','3') +#define VC_CONTAINER_CODEC_H264 VC_FOURCC('h','2','6','4') +#define VC_CONTAINER_CODEC_MVC VC_FOURCC('m','v','c',' ') +#define VC_CONTAINER_CODEC_WMV1 VC_FOURCC('w','m','v','1') +#define VC_CONTAINER_CODEC_WMV2 VC_FOURCC('w','m','v','2') +#define VC_CONTAINER_CODEC_WMV3 VC_FOURCC('w','m','v','3') +#define VC_CONTAINER_CODEC_WVC1 VC_FOURCC('w','v','c','1') +#define VC_CONTAINER_CODEC_WMVA VC_FOURCC('w','m','v','a') +#define VC_CONTAINER_CODEC_MJPEG VC_FOURCC('m','j','p','g') +#define VC_CONTAINER_CODEC_MJPEGA VC_FOURCC('m','j','p','a') +#define VC_CONTAINER_CODEC_MJPEGB VC_FOURCC('m','j','p','b') +#define VC_CONTAINER_CODEC_THEORA VC_FOURCC('t','h','e','o') +#define VC_CONTAINER_CODEC_VP3 VC_FOURCC('v','p','3',' ') +#define VC_CONTAINER_CODEC_VP6 VC_FOURCC('v','p','6',' ') +#define VC_CONTAINER_CODEC_VP7 VC_FOURCC('v','p','7',' ') +#define VC_CONTAINER_CODEC_VP8 VC_FOURCC('v','p','8',' ') +#define VC_CONTAINER_CODEC_RV10 VC_FOURCC('r','v','1','0') +#define VC_CONTAINER_CODEC_RV20 VC_FOURCC('r','v','2','0') +#define VC_CONTAINER_CODEC_RV30 VC_FOURCC('r','v','3','0') +#define VC_CONTAINER_CODEC_RV40 VC_FOURCC('r','v','4','0') +#define VC_CONTAINER_CODEC_AVS VC_FOURCC('a','v','s',' ') +#define VC_CONTAINER_CODEC_SPARK VC_FOURCC('s','p','r','k') +#define VC_CONTAINER_CODEC_DIRAC VC_FOURCC('d','r','a','c') + +#define VC_CONTAINER_CODEC_YUV VC_FOURCC('y','u','v',' ') +#define VC_CONTAINER_CODEC_I420 VC_FOURCC('I','4','2','0') +#define VC_CONTAINER_CODEC_YV12 VC_FOURCC('Y','V','1','2') +#define VC_CONTAINER_CODEC_I422 VC_FOURCC('I','4','2','2') +#define VC_CONTAINER_CODEC_YUYV VC_FOURCC('Y','U','Y','V') +#define VC_CONTAINER_CODEC_YVYU VC_FOURCC('Y','V','Y','U') +#define VC_CONTAINER_CODEC_UYVY VC_FOURCC('U','Y','V','Y') +#define VC_CONTAINER_CODEC_VYUY VC_FOURCC('V','Y','U','Y') +#define VC_CONTAINER_CODEC_NV12 VC_FOURCC('N','V','1','2') +#define VC_CONTAINER_CODEC_NV21 VC_FOURCC('N','V','2','1') +#define VC_CONTAINER_CODEC_ARGB VC_FOURCC('A','R','G','B') +#define VC_CONTAINER_CODEC_RGBA VC_FOURCC('R','G','B','A') +#define VC_CONTAINER_CODEC_ABGR VC_FOURCC('A','B','G','R') +#define VC_CONTAINER_CODEC_BGRA VC_FOURCC('B','G','R','A') +#define VC_CONTAINER_CODEC_RGB16 VC_FOURCC('R','G','B','2') +#define VC_CONTAINER_CODEC_RGB24 VC_FOURCC('R','G','B','3') +#define VC_CONTAINER_CODEC_RGB32 VC_FOURCC('R','G','B','4') +#define VC_CONTAINER_CODEC_BGR16 VC_FOURCC('B','G','R','2') +#define VC_CONTAINER_CODEC_BGR24 VC_FOURCC('B','G','R','3') +#define VC_CONTAINER_CODEC_BGR32 VC_FOURCC('B','G','R','4') +#define VC_CONTAINER_CODEC_YUVUV128 VC_FOURCC('S','A','N','D') + +#define VC_CONTAINER_CODEC_JPEG VC_FOURCC('j','p','e','g') +#define VC_CONTAINER_CODEC_PNG VC_FOURCC('p','n','g',' ') +#define VC_CONTAINER_CODEC_GIF VC_FOURCC('g','i','f',' ') +#define VC_CONTAINER_CODEC_PPM VC_FOURCC('p','p','m',' ') +#define VC_CONTAINER_CODEC_TGA VC_FOURCC('t','g','a',' ') +#define VC_CONTAINER_CODEC_BMP VC_FOURCC('b','m','p',' ') + +/* Audio */ +#define VC_CONTAINER_CODEC_PCM_UNSIGNED_BE VC_FOURCC('P','C','M','U') +#define VC_CONTAINER_CODEC_PCM_UNSIGNED_LE VC_FOURCC('p','c','m','u') +#define VC_CONTAINER_CODEC_PCM_SIGNED_BE VC_FOURCC('P','C','M','S') +#define VC_CONTAINER_CODEC_PCM_SIGNED_LE VC_FOURCC('p','c','m','s') +#define VC_CONTAINER_CODEC_PCM_FLOAT_BE VC_FOURCC('P','C','M','F') +#define VC_CONTAINER_CODEC_PCM_FLOAT_LE VC_FOURCC('p','c','m','f') +/* Defines for native endianness */ +#ifdef VC_CONTAINER_IS_BIG_ENDIAN +#define VC_CONTAINER_CODEC_PCM_UNSIGNED VC_CONTAINER_CODEC_PCM_UNSIGNED_BE +#define VC_CONTAINER_CODEC_PCM_SIGNED VC_CONTAINER_CODEC_PCM_SIGNED_BE +#define VC_CONTAINER_CODEC_PCM_FLOAT VC_CONTAINER_CODEC_PCM_FLOAT_BE +#else +#define VC_CONTAINER_CODEC_PCM_UNSIGNED VC_CONTAINER_CODEC_PCM_UNSIGNED_LE +#define VC_CONTAINER_CODEC_PCM_SIGNED VC_CONTAINER_CODEC_PCM_SIGNED_LE +#define VC_CONTAINER_CODEC_PCM_FLOAT VC_CONTAINER_CODEC_PCM_FLOAT_LE +#endif + +#define VC_CONTAINER_CODEC_MPGA VC_FOURCC('m','p','g','a') +#define VC_CONTAINER_CODEC_MP4A VC_FOURCC('m','p','4','a') +#define VC_CONTAINER_CODEC_ALAW VC_FOURCC('a','l','a','w') +#define VC_CONTAINER_CODEC_MULAW VC_FOURCC('u','l','a','w') +#define VC_CONTAINER_CODEC_ADPCM_MS VC_FOURCC('m','s',0x0,0x2) +#define VC_CONTAINER_CODEC_ADPCM_IMA_MS VC_FOURCC('m','s',0x0,0x1) +#define VC_CONTAINER_CODEC_ADPCM_SWF VC_FOURCC('a','s','w','f') +#define VC_CONTAINER_CODEC_WMA1 VC_FOURCC('w','m','a','1') +#define VC_CONTAINER_CODEC_WMA2 VC_FOURCC('w','m','a','2') +#define VC_CONTAINER_CODEC_WMAP VC_FOURCC('w','m','a','p') +#define VC_CONTAINER_CODEC_WMAL VC_FOURCC('w','m','a','l') +#define VC_CONTAINER_CODEC_WMAV VC_FOURCC('w','m','a','v') +#define VC_CONTAINER_CODEC_AMRNB VC_FOURCC('a','m','r','n') +#define VC_CONTAINER_CODEC_AMRWB VC_FOURCC('a','m','r','w') +#define VC_CONTAINER_CODEC_AMRWBP VC_FOURCC('a','m','r','p') +#define VC_CONTAINER_CODEC_AC3 VC_FOURCC('a','c','3',' ') +#define VC_CONTAINER_CODEC_EAC3 VC_FOURCC('e','a','c','3') +#define VC_CONTAINER_CODEC_DTS VC_FOURCC('d','t','s',' ') +#define VC_CONTAINER_CODEC_MLP VC_FOURCC('m','l','p',' ') +#define VC_CONTAINER_CODEC_FLAC VC_FOURCC('f','l','a','c') +#define VC_CONTAINER_CODEC_VORBIS VC_FOURCC('v','o','r','b') +#define VC_CONTAINER_CODEC_SPEEX VC_FOURCC('s','p','x',' ') +#define VC_CONTAINER_CODEC_ATRAC3 VC_FOURCC('a','t','r','3') +#define VC_CONTAINER_CODEC_ATRACX VC_FOURCC('a','t','r','x') +#define VC_CONTAINER_CODEC_ATRACL VC_FOURCC('a','t','r','l') +#define VC_CONTAINER_CODEC_MIDI VC_FOURCC('m','i','d','i') +#define VC_CONTAINER_CODEC_EVRC VC_FOURCC('e','v','r','c') +#define VC_CONTAINER_CODEC_NELLYMOSER VC_FOURCC('n','e','l','y') +#define VC_CONTAINER_CODEC_QCELP VC_FOURCC('q','c','e','l') + +/* Text */ +#define VC_CONTAINER_CODEC_TEXT VC_FOURCC('t','e','x','t') +#define VC_CONTAINER_CODEC_SSA VC_FOURCC('s','s','a',' ') +#define VC_CONTAINER_CODEC_USF VC_FOURCC('u','s','f',' ') +#define VC_CONTAINER_CODEC_VOBSUB VC_FOURCC('v','s','u','b') + +#define VC_CONTAINER_CODEC_UNKNOWN VC_FOURCC('u','n','k','n') + +/* Codec variants */ + +/** ISO 14496-10 Annex B byte stream format */ +#define VC_CONTAINER_VARIANT_H264_DEFAULT 0 +/** ISO 14496-15 AVC format (used in mp4/mkv and other containers) */ +#define VC_CONTAINER_VARIANT_H264_AVC1 VC_FOURCC('a','v','c','C') +/** Implicitly delineated NAL units without emulation prevention */ +#define VC_CONTAINER_VARIANT_H264_RAW VC_FOURCC('r','a','w',' ') + +/** MPEG 1/2 Audio - Layer unknown */ +#define VC_CONTAINER_VARIANT_MPGA_DEFAULT 0 +/** MPEG 1/2 Audio - Layer 1 */ +#define VC_CONTAINER_VARIANT_MPGA_L1 VC_FOURCC('l','1',' ',' ') +/** MPEG 1/2 Audio - Layer 2 */ +#define VC_CONTAINER_VARIANT_MPGA_L2 VC_FOURCC('l','2',' ',' ') +/** MPEG 1/2 Audio - Layer 3 */ +#define VC_CONTAINER_VARIANT_MPGA_L3 VC_FOURCC('l','3',' ',' ') + +/** Converts a WaveFormat ID into a VC_CONTAINER_FOURCC_T. + * + * \param waveformat_id WaveFormat ID to convert + * \return a valid VC_CONTAINER_FOURCC_T or VC_CONTAINER_CODEC_UNKNOWN if no mapping was found. + */ +VC_CONTAINER_FOURCC_T waveformat_to_codec(uint16_t waveformat_id); + +/** Converts a VC_CONTAINER_FOURCC_T into a WaveFormat ID. + * + * \param codec VC_CONTAINER_FOURCC_T to convert + * \return a valid WaveFormat ID of 0 if no mapping was found. + */ +uint16_t codec_to_waveformat(VC_CONTAINER_FOURCC_T codec); + +/** Tries to convert a generic fourcc into a VC_CONTAINER_FOURCC_T. + * + * \param fourcc fourcc to convert + * \return a valid VC_CONTAINER_FOURCC_T or VC_CONTAINER_CODEC_UNKNOWN if no mapping was found. + */ +VC_CONTAINER_FOURCC_T fourcc_to_codec(uint32_t fourcc); + +uint32_t codec_to_fourcc(VC_CONTAINER_FOURCC_T codec); + +/** Tries to convert VideoForWindows fourcc into a VC_CONTAINER_FOURCC_T. + * + * \param fourcc vfw fourcc to convert + * \return a valid VC_CONTAINER_FOURCC_T or VC_CONTAINER_CODEC_UNKNOWN if no mapping was found. + */ +VC_CONTAINER_FOURCC_T vfw_fourcc_to_codec(uint32_t fourcc); + +/** Tries to convert a VC_CONTAINER_FOURCC_T into a VideoForWindows fourcc. + * + * \param codec VC_CONTAINER_FOURCC_T to convert + * \return a valid vfw fourcc or 0 if no mapping was found. + */ +uint32_t codec_to_vfw_fourcc(VC_CONTAINER_FOURCC_T codec); + +#ifdef __cplusplus +} +#endif + +#endif /* VC_CONTAINERS_CODECS_H */ diff --git a/containers/containers_types.h b/containers/containers_types.h new file mode 100644 index 0000000..4cf6666 --- /dev/null +++ b/containers/containers_types.h @@ -0,0 +1,100 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_TYPES_H +#define VC_CONTAINERS_TYPES_H + +/** \file containers_types.h + * Definition of types used by the containers API + */ + +#include +#include +#include +#include + +#if defined( __STDC__ ) && defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#include + +#elif defined( __GNUC__ ) +#include +#include + +#elif defined(_MSC_VER) +#include +#if (_MSC_VER < 1700) +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#endif +#define PRIu16 "u" +#define PRIu32 "u" +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" + +#else +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed long int32_t; +typedef unsigned long uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +#endif + +/* C99 64bits integers */ +#ifndef INT64_C +# define INT64_C(value) value##LL +# define UINT64_C(value) value##ULL +#endif + +/* C99 boolean */ +#ifndef __cplusplus +#ifndef bool +# define bool int +#endif +#ifndef true +# define true 1 +#endif +#ifndef false +# define false 0 +#endif +#endif /* __cplusplus */ + +/* FIXME: should be different for big endian */ +#define VC_FOURCC(a,b,c,d) ((a) | (b << 8) | (c << 16) | (d << 24)) + +#endif /* VC_CONTAINERS_TYPES_H */ diff --git a/containers/core/containers.c b/containers/core/containers.c new file mode 100644 index 0000000..3693e35 --- /dev/null +++ b/containers/core/containers.c @@ -0,0 +1,637 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_filters.h" +#include "containers/core/containers_loader.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_utils.h" + +#define WRITER_SPACE_SAFETY_MARGIN (10*1024) +#define PACKETIZER_BUFFER_SIZE (32*1024) + +/*****************************************************************************/ +VC_CONTAINER_T *vc_container_open_reader_with_io( struct VC_CONTAINER_IO_T *io, + const char *uri, VC_CONTAINER_STATUS_T *p_status, + VC_CONTAINER_PROGRESS_REPORT_FUNC_T pfn_progress, void *progress_userdata) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_T *p_ctx = 0; + const char *extension; + + VC_CONTAINER_PARAM_UNUSED(pfn_progress); + VC_CONTAINER_PARAM_UNUSED(progress_userdata); + VC_CONTAINER_PARAM_UNUSED(uri); + + /* Sanity check the i/o */ + if (!io || !io->pf_read || !io->pf_seek) + { + LOG_ERROR(0, "invalid i/o instance: %p", io); + status = VC_CONTAINER_ERROR_INVALID_ARGUMENT; + goto error; + } + + /* Allocate our context before trying out the different readers / writers */ + p_ctx = malloc( sizeof(*p_ctx) + sizeof(*p_ctx->priv) + sizeof(*p_ctx->drm)); + if(!p_ctx) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(p_ctx, 0, sizeof(*p_ctx) + sizeof(*p_ctx->priv) + sizeof(*p_ctx->drm)); + p_ctx->priv = (VC_CONTAINER_PRIVATE_T *)(p_ctx + 1); + p_ctx->priv->verbosity = vc_container_log_get_default_verbosity(); + p_ctx->drm = (VC_CONTAINER_DRM_T *)(p_ctx->priv + 1); + p_ctx->size = io->size; + p_ctx->priv->io = io; + p_ctx->priv->uri = io->uri_parts; + + /* If the uri has an extension, use it as a hint when loading the container */ + extension = vc_uri_path_extension(p_ctx->priv->uri); + /* If the user has specified a container, then use that instead */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + status = vc_container_load_reader(p_ctx, extension); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + p_ctx->priv->drm_filter = vc_container_filter_open(VC_FOURCC('d','r','m',' '), + VC_FOURCC('u','n','k','n'), p_ctx, &status); + if (status != VC_CONTAINER_SUCCESS) + { + /* Some other problem occurred aside from the filter not being appropriate or + the stream not needing it. */ + if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; + + /* Report no DRM and continue as normal */ + p_ctx->drm = NULL; + status = VC_CONTAINER_SUCCESS; + } + +end: + if(p_status) *p_status = status; + return p_ctx; + +error: + if (p_ctx) + { + p_ctx->priv->io = NULL; /* The i/o doesn't belong to us */ + vc_container_close(p_ctx); + p_ctx = NULL; + } + goto end; +} + +/*****************************************************************************/ +VC_CONTAINER_T *vc_container_open_reader( const char *uri, VC_CONTAINER_STATUS_T *p_status, + VC_CONTAINER_PROGRESS_REPORT_FUNC_T pfn_progress, void *progress_userdata) +{ + VC_CONTAINER_IO_T *io; + VC_CONTAINER_T *ctx; + + /* Start by opening the URI */ + io = vc_container_io_open( uri, VC_CONTAINER_IO_MODE_READ, p_status ); + if (!io) + return 0; + + ctx = vc_container_open_reader_with_io( io, uri, p_status, pfn_progress, progress_userdata); + if (!ctx) + vc_container_io_close(io); + return ctx; +} + +/*****************************************************************************/ +VC_CONTAINER_T *vc_container_open_writer( const char *uri, VC_CONTAINER_STATUS_T *p_status, + VC_CONTAINER_PROGRESS_REPORT_FUNC_T pfn_progress, void *progress_userdata) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_T *p_ctx = 0; + VC_CONTAINER_IO_T *io; + const char *extension; + VC_CONTAINER_PARAM_UNUSED(pfn_progress); + VC_CONTAINER_PARAM_UNUSED(progress_userdata); + + /* Start by opening the URI */ + io = vc_container_io_open( uri, VC_CONTAINER_IO_MODE_WRITE, &status ); + if(!io) goto error; + + /* Make sure we have enough space available to start writing */ + if(io->max_size && io->max_size < WRITER_SPACE_SAFETY_MARGIN) + { + LOG_DEBUG(p_ctx, "not enough space available to open a writer"); + status = VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + goto error; + } + + /* Allocate our context before trying out the different readers / writers */ + p_ctx = malloc( sizeof(*p_ctx) + sizeof(*p_ctx->priv)); + if(!p_ctx) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(p_ctx, 0, sizeof(*p_ctx) + sizeof(*p_ctx->priv)); + p_ctx->priv = (VC_CONTAINER_PRIVATE_T *)(p_ctx + 1); + p_ctx->priv->verbosity = vc_container_log_get_default_verbosity(); + p_ctx->priv->io = io; + p_ctx->priv->uri = io->uri_parts; + io = NULL; /* io now owned by the context */ + + /* If the uri has an extension, use it as a hint when loading the container */ + extension = vc_uri_path_extension(p_ctx->priv->uri); + /* If the user has specified a container, then use that instead */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + status = vc_container_load_writer(p_ctx, extension); + if(status != VC_CONTAINER_SUCCESS) goto error; + + end: + if(p_status) *p_status = status; + return p_ctx; + +error: + if(io) vc_container_io_close(io); + if (p_ctx) vc_container_close(p_ctx); + p_ctx = NULL; + goto end; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_close( VC_CONTAINER_T *p_ctx ) +{ + unsigned int i; + + if(!p_ctx) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + for(i = 0; i < p_ctx->tracks_num; i++) + if(p_ctx->tracks[i]->priv->packetizer) + vc_packetizer_close(p_ctx->tracks[i]->priv->packetizer); + if(p_ctx->priv->packetizer_buffer) free(p_ctx->priv->packetizer_buffer); + if(p_ctx->priv->drm_filter) vc_container_filter_close(p_ctx->priv->drm_filter); + if(p_ctx->priv->pf_close) p_ctx->priv->pf_close(p_ctx); + if(p_ctx->priv->io) vc_container_io_close(p_ctx->priv->io); + if(p_ctx->priv->module_handle) vc_container_unload(p_ctx); + for(i = 0; i < p_ctx->meta_num; i++) free(p_ctx->meta[i]); + if(p_ctx->meta_num) free(p_ctx->meta); + p_ctx->meta_num = 0; + free(p_ctx); + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T container_read_packet( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) +{ + VC_CONTAINER_STATUS_T status; + + while(1) + { + status = p_ctx->priv->pf_read(p_ctx, p_packet, flags); + if(status == VC_CONTAINER_ERROR_CONTINUE) + continue; + + if(!p_packet || (flags & VC_CONTAINER_READ_FLAG_SKIP)) + return status; /* We've just been requested to skip the data */ + + if(status != VC_CONTAINER_SUCCESS) + return status; + + /* Skip data from out of bounds tracks, disabled tracks or packets that are encrypted + and cannot be decrypted */ + if(p_packet->track >= p_ctx->tracks_num || + !p_ctx->tracks[p_packet->track]->is_enabled || + ((p_packet->flags & VC_CONTAINER_PACKET_FLAG_ENCRYPTED) && !p_ctx->priv->drm_filter)) + { + if(flags & VC_CONTAINER_READ_FLAG_INFO) + status = p_ctx->priv->pf_read(p_ctx, p_packet, VC_CONTAINER_READ_FLAG_SKIP); + if(status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_CONTINUE) + continue; + } + if(status != VC_CONTAINER_SUCCESS) + return status; + + if(p_ctx->priv->drm_filter) + status = vc_container_filter_process(p_ctx->priv->drm_filter, p_packet); + + break; + } + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_read( VC_CONTAINER_T *p_ctx, VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_CONTINUE; + VC_PACKETIZER_FLAGS_T packetizer_flags = 0; + VC_PACKETIZER_T *packetizer; + uint32_t force = flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK; + unsigned int i; + + if(!p_packet && !(flags & VC_CONTAINER_READ_FLAG_SKIP)) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + if(!p_packet && (flags & VC_CONTAINER_READ_FLAG_INFO)) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + if(p_packet && !p_packet->data && !(flags & (VC_CONTAINER_READ_FLAG_INFO | VC_CONTAINER_READ_FLAG_SKIP))) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + if((flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) && + (!p_packet || p_packet->track >= p_ctx->tracks_num || !p_ctx->tracks[p_packet->track]->is_enabled)) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + /* Always having a packet structure to work with simplifies things */ + if(!p_packet) + p_packet = &p_ctx->priv->packetizer_packet; + + /* Simple/Fast case first */ + if(!p_ctx->priv->packetizing) + { + status = container_read_packet( p_ctx, p_packet, flags ); + goto end; + } + + if(flags & VC_CONTAINER_READ_FLAG_INFO) + packetizer_flags |= VC_PACKETIZER_FLAG_INFO; + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + packetizer_flags |= VC_PACKETIZER_FLAG_SKIP; + + /* Loop through all the packetized tracks first to see if we've got any + * data to consume there */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_PACKETIZER_T *packetizer = p_ctx->tracks[i]->priv->packetizer; + if(!p_ctx->tracks[i]->is_enabled || !packetizer || + (force && i != p_packet->track)) + continue; + + status = vc_packetizer_read(packetizer, p_packet, packetizer_flags); + p_packet->track = i; + if(status == VC_CONTAINER_SUCCESS) + break; + } + if(i < p_ctx->tracks_num) /* We've got some data */ + goto end; + + /* Let's go and read some data from the actual container */ + while(1) + { + VC_CONTAINER_PACKET_T *tmp = &p_ctx->priv->packetizer_packet; + tmp->track = p_packet->track; + + /* Let's check what the container has to offer */ + status = container_read_packet( p_ctx, tmp, force|VC_PACKETIZER_FLAG_INFO ); + if(status != VC_CONTAINER_SUCCESS) + return status; + + if(!p_ctx->tracks[tmp->track]->priv->packetizer) + { + status = container_read_packet( p_ctx, p_packet, flags ); + break; + } + + /* Feed data from the container onto the packetizer */ + packetizer = p_ctx->tracks[tmp->track]->priv->packetizer; + + tmp->data = p_ctx->priv->packetizer_buffer; + tmp->buffer_size = PACKETIZER_BUFFER_SIZE; + tmp->size = 0; + status = container_read_packet( p_ctx, tmp, force ); + if(status != VC_CONTAINER_SUCCESS) + return status; + + p_packet->track = tmp->track; + vc_packetizer_push(packetizer, tmp); + vc_packetizer_pop(packetizer, &tmp, VC_PACKETIZER_FLAG_FORCE_RELEASE_INPUT); + + status = vc_packetizer_read(packetizer, p_packet, packetizer_flags); + if(status == VC_CONTAINER_SUCCESS) + break; + } + + end: + if(status != VC_CONTAINER_SUCCESS) + return status; + + if(p_packet && p_packet->dts > p_ctx->position) + p_ctx->position = p_packet->dts; + if(p_packet && p_packet->pts > p_ctx->position) + p_ctx->position = p_packet->pts; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_write( VC_CONTAINER_T *p_ctx, VC_CONTAINER_PACKET_T *p_packet ) +{ + VC_CONTAINER_STATUS_T status; + void * p_metadata_buffer = NULL; + uint32_t metadata_length = 0; + + /* TODO: check other similar argument errors and non-stateless errors */ + if (!p_packet || !p_packet->data || p_packet->track >= p_ctx->tracks_num) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + /* Check for a previous error */ + if(p_ctx->priv->status != VC_CONTAINER_SUCCESS && p_ctx->priv->status != VC_CONTAINER_ERROR_NOT_READY) + return p_ctx->priv->status; + + /* Check we have enough space to write the data */ + if(p_ctx->priv->max_size && + p_ctx->size + p_packet->size + WRITER_SPACE_SAFETY_MARGIN > p_ctx->priv->max_size) + {status = VC_CONTAINER_ERROR_LIMIT_REACHED; goto end;} + if(p_ctx->priv->io->max_size && + p_ctx->size + p_packet->size + WRITER_SPACE_SAFETY_MARGIN + + (p_ctx->priv->tmp_io ? p_ctx->priv->tmp_io->offset : 0) > p_ctx->priv->io->max_size) + {status = VC_CONTAINER_ERROR_OUT_OF_SPACE; goto end;} + + /* If a filter is created, then send the packet to the filter for encryption. */ + if(p_ctx->priv->drm_filter) + { + status = vc_container_filter_process(p_ctx->priv->drm_filter, p_packet); + + if(status == VC_CONTAINER_SUCCESS) + { + /* Get the encryption metadata and send it to the output first. */ + if(vc_container_control(p_ctx, VC_CONTAINER_CONTROL_GET_DRM_METADATA, + &p_metadata_buffer, &metadata_length) == VC_CONTAINER_SUCCESS && metadata_length > 0) + { + /* Make a packet up with the metadata in the payload and write it. */ + VC_CONTAINER_PACKET_T metadata_packet; + metadata_packet.data = p_metadata_buffer; + metadata_packet.buffer_size = metadata_length; + metadata_packet.size = metadata_length; + metadata_packet.frame_size = p_packet->frame_size + metadata_length; + metadata_packet.pts = p_packet->pts; + metadata_packet.dts = p_packet->dts; + metadata_packet.num = p_packet->num; + metadata_packet.track = p_packet->track; + /* As this packet is written first, we must transfer any frame start + flag from the following packet. Also, this packet can never have the end flag set. */ + metadata_packet.flags = p_packet->flags & ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + + p_packet->pts = p_packet->dts = VC_CONTAINER_TIME_UNKNOWN; + p_packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_START; + if(p_ctx->priv->pf_write(p_ctx, &metadata_packet) != VC_CONTAINER_SUCCESS) goto end; + } + } + else if (status != VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION) + { + /* Encryption was appropriate but a problem has occurred. Skip the write of data + to the io and return the status to the caller. */ + goto end; + } + } + + status = p_ctx->priv->pf_write(p_ctx, p_packet); + + end: + p_ctx->priv->status = status; + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_seek( VC_CONTAINER_T *p_ctx, int64_t *p_offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_STATUS_T status; + int64_t offset = *p_offset; + unsigned int i; + + /* Reset all packetizers */ + for(i = 0; i < p_ctx->tracks_num; i++) + if(p_ctx->tracks[i]->priv->packetizer) + vc_packetizer_reset(p_ctx->tracks[i]->priv->packetizer); + + status = p_ctx->priv->pf_seek(p_ctx, p_offset, mode, flags); + + /* */ + if(status == VC_CONTAINER_SUCCESS && (flags & VC_CONTAINER_SEEK_FLAG_FORWARD) && + *p_offset < offset) + { + LOG_DEBUG(p_ctx, "container didn't seek forward as requested (%"PRIi64"/%"PRIi64")", + *p_offset, offset); + for(i = 1; i <= 5 && *p_offset < offset; i++) + { + *p_offset = offset + i * i * 100000; + status = p_ctx->priv->pf_seek(p_ctx, p_offset, mode, flags); + if(status != VC_CONTAINER_SUCCESS) break; + } + } + + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_control( VC_CONTAINER_T *p_ctx, VC_CONTAINER_CONTROL_T operation, ... ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + va_list args; + + va_start( args, operation ); + + if(operation == VC_CONTAINER_CONTROL_ENCRYPT_TRACK) + { + VC_CONTAINER_FOURCC_T type = va_arg(args, VC_CONTAINER_FOURCC_T); + + uint32_t track_num = va_arg(args, uint32_t); + void *p_drm_data = va_arg(args, void *); + int drm_data_size = va_arg(args, uint32_t); + + if(!p_ctx->priv->drm_filter && track_num < p_ctx->tracks_num) + { + VC_CONTAINER_TRACK_T * p_track = p_ctx->tracks[track_num]; + + if ((status = vc_container_track_allocate_drmdata(p_ctx, p_track, drm_data_size)) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "failed to allocate memory for 'drm data' (%d bytes)", drm_data_size); + goto end; + } + memcpy(p_track->priv->drmdata, p_drm_data, drm_data_size); + + /* Track encryption enabled and no filter - create one now. */ + p_ctx->priv->drm_filter = vc_container_filter_open(VC_FOURCC('d','r','m',' '), type, p_ctx, &status); + if(status != VC_CONTAINER_SUCCESS) + goto end; + + status = vc_container_filter_control(p_ctx->priv->drm_filter, operation, track_num); + } + } + else if(operation == VC_CONTAINER_CONTROL_GET_DRM_METADATA) + { + void *p_drm_data = va_arg(args, void *); + int *drm_data_size = va_arg(args, int *); + status = vc_container_filter_control(p_ctx->priv->drm_filter, operation, p_drm_data, drm_data_size); + } + + if(status == VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION && p_ctx->priv->pf_control) + status = p_ctx->priv->pf_control(p_ctx, operation, args); + + /* If the request has already been handled then we're done */ + if(status != VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION) + goto end; + + switch(operation) + { + case VC_CONTAINER_CONTROL_DRM_PLAY: + if (p_ctx->priv->drm_filter) + status = vc_container_filter_control(p_ctx->priv->drm_filter, operation, args); + break; + + case VC_CONTAINER_CONTROL_SET_MAXIMUM_SIZE: + p_ctx->priv->max_size = (uint64_t)va_arg( args, uint64_t ); + if(p_ctx->priv->io->max_size && + p_ctx->priv->max_size > p_ctx->priv->io->max_size) + p_ctx->priv->max_size = p_ctx->priv->io->max_size; + status = VC_CONTAINER_SUCCESS; + break; + + case VC_CONTAINER_CONTROL_TRACK_PACKETIZE: + { + unsigned int track_num = va_arg(args, unsigned int); + VC_CONTAINER_FOURCC_T fourcc = va_arg(args, VC_CONTAINER_FOURCC_T); + VC_CONTAINER_TRACK_T *p_track; + + if(track_num >= p_ctx->tracks_num) + { + status = VC_CONTAINER_ERROR_INVALID_ARGUMENT; + break; + } + if(p_ctx->tracks[track_num]->priv->packetizer) + { + status = VC_CONTAINER_SUCCESS; + break; + } + + p_track = p_ctx->tracks[track_num]; + p_track->priv->packetizer = vc_packetizer_open( p_track->format, fourcc, &status ); + if(!p_track->priv->packetizer) + break; + + if(!p_ctx->priv->packetizer_buffer) + { + p_ctx->priv->packetizer_buffer = malloc(PACKETIZER_BUFFER_SIZE); + if(!p_ctx->priv->packetizer_buffer) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + vc_packetizer_close(p_track->priv->packetizer); + p_track->priv->packetizer = NULL; + break; + } + } + + vc_container_format_copy(p_track->format, p_track->priv->packetizer->out, + p_track->format->extradata_size); + p_track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + p_ctx->priv->packetizing = true; + } + break; + + default: break; + } + + if (status == VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION) + status = vc_container_io_control_list(p_ctx->priv->io, operation, args); + + end: + va_end( args ); + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_TRACK_T *vc_container_allocate_track( VC_CONTAINER_T *context, unsigned int extra_size ) +{ + VC_CONTAINER_TRACK_T *p_ctx; + unsigned int size; + VC_CONTAINER_PARAM_UNUSED(context); + + size = sizeof(*p_ctx) + sizeof(*p_ctx->priv) + sizeof(*p_ctx->format) + + sizeof(*p_ctx->format->type) + extra_size; + + p_ctx = malloc(size); + if(!p_ctx) return 0; + + memset(p_ctx, 0, size); + p_ctx->priv = (VC_CONTAINER_TRACK_PRIVATE_T *)(p_ctx + 1); + p_ctx->format = (VC_CONTAINER_ES_FORMAT_T *)(p_ctx->priv + 1); + p_ctx->format->type = (void *)(p_ctx->format + 1); + p_ctx->priv->module = (struct VC_CONTAINER_TRACK_MODULE_T *)(p_ctx->format->type + 1); + return p_ctx; +} + +/*****************************************************************************/ +void vc_container_free_track( VC_CONTAINER_T *context, VC_CONTAINER_TRACK_T *p_track ) +{ + VC_CONTAINER_PARAM_UNUSED(context); + if(p_track) + { + if(p_track->priv->extradata) free(p_track->priv->extradata); + if(p_track->priv->drmdata) free(p_track->priv->drmdata); + free(p_track); + } +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_track_allocate_extradata( VC_CONTAINER_T *context, + VC_CONTAINER_TRACK_T *p_track, unsigned int extra_size ) +{ + VC_CONTAINER_PARAM_UNUSED(context); + + /* Sanity check the size of the extra data */ + if(extra_size > 100*1024) return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + /* Check if we need to allocate a buffer */ + if(extra_size > p_track->priv->extradata_size) + { + p_track->priv->extradata_size = 0; + if(p_track->priv->extradata) free(p_track->priv->extradata); + p_track->priv->extradata = malloc(extra_size); + p_track->format->extradata = p_track->priv->extradata; + if(!p_track->priv->extradata) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + p_track->priv->extradata_size = extra_size; + } + p_track->format->extradata = p_track->priv->extradata; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_track_allocate_drmdata( VC_CONTAINER_T *context, + VC_CONTAINER_TRACK_T *p_track, unsigned int size ) +{ + VC_CONTAINER_PARAM_UNUSED(context); + + /* Sanity check the size of the drm data */ + if(size > 200*1024) return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + /* Check if we need to allocate a buffer */ + if(size > p_track->priv->drmdata_size) + { + p_track->priv->drmdata_size = 0; + if(p_track->priv->drmdata) free(p_track->priv->drmdata); + p_track->priv->drmdata = malloc(size); + if(!p_track->priv->drmdata) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + p_track->priv->drmdata_size = size; + } + + return VC_CONTAINER_SUCCESS; +} diff --git a/containers/core/containers_bits.c b/containers/core/containers_bits.c new file mode 100644 index 0000000..30f8650 --- /dev/null +++ b/containers/core/containers_bits.c @@ -0,0 +1,490 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "containers/core/containers_bits.h" +#include "containers/core/containers_common.h" + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT +#include "containers/core/containers_logging.h" +#endif + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT +/** String used for indentation. If more spaces are needed, just add them. */ +#define INDENT_SPACES_STRING "> " +#define INDENT_SPACES_LENGTH (sizeof(INDENT_SPACES_STRING) - 1) +#endif /* ENABLE_CONTAINERS_LOG_FORMAT */ + +/****************************************************************************** +Type definitions +******************************************************************************/ + +/****************************************************************************** +Function prototypes +******************************************************************************/ + +/****************************************************************************** +Local Functions +******************************************************************************/ + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT + +/**************************************************************************//** + * Returns a string that indicates whether the bit stream is valid or not. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return A string indicating the validity of the stream. + */ +static const char * vc_container_bits_valid_str( VC_CONTAINER_BITS_T *bit_stream ) +{ + return vc_container_bits_valid(bit_stream) ? "" : " - stream invalid"; +} + +/**************************************************************************//** + * Returns a string of spaces the length of which is determined by the + * parameter. + * The length is limited to a certain size, above which a greater than symbol + * prefixes the maximum number of spaces. + * + * \param length The required length of the string. + * \return A string indicating the validity of the stream. + */ +static const char * vc_container_bits_indent_str(uint32_t length) +{ + uint32_t str_length = length; + + if (str_length > INDENT_SPACES_LENGTH) + str_length = INDENT_SPACES_LENGTH; + + return INDENT_SPACES_STRING + (INDENT_SPACES_LENGTH - str_length); +} + +#endif /* ENABLE_CONTAINERS_LOG_FORMAT */ + +/**************************************************************************//** + * Returns the number of consecutive zero bits in the stream. + * the zero bits are terminated either by a one bit, or the end of the stream. + * In the former case, the zero bits and the terminating one bit are removed + * from the stream. + * In the latter case, the stream becomes invalid. The stream also becomes + * invalid if there are not as many bits after the one bit as zero bits before + * it. + * If the stream is already or becomes invalid, zero is returned. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return The number of consecutive zero bits, or zero if the stream is + * invalid. + */ +static uint32_t vc_container_bits_get_leading_zero_bits( VC_CONTAINER_BITS_T *bit_stream ) +{ + uint32_t leading_zero_bits; + uint32_t bits_left = vc_container_bits_available(bit_stream); + uint32_t bits; + uint8_t mask, current_byte; + + if (!bits_left) + return vc_container_bits_invalidate(bit_stream); + + /* Cache 'bits' field to avoid repeated pointer access */ + bits = bit_stream->bits; + if (bits) + { + current_byte = *bit_stream->buffer; + mask = 1 << (bits - 1); + } else { + /* Initialize variables to placate the compiler */ + current_byte = 0; + mask = 0; + } + + /* Scan for the first one bit, counting the number of zeroes. This gives the + * number of further bits after the one that are part of the value. See + * section 9.1 of ITU-T REC H.264 201003 for more details. */ + + for (leading_zero_bits = 0; leading_zero_bits < bits_left; leading_zero_bits++) + { + if (!bits) + { + if (!bit_stream->bytes) + return vc_container_bits_invalidate(bit_stream); + bit_stream->bytes--; + current_byte = *(++bit_stream->buffer); + bits = 8; + mask = 0x80; + } + + bits--; + bits_left--; + if (current_byte & mask) + break; /* Found the marker bit */ + + mask >>= 1; + } + + /* Check enough bits are left in the stream for the value. */ + if (leading_zero_bits > bits_left) + return vc_container_bits_invalidate(bit_stream); + + /* Return cached value of bits to the stream */ + bit_stream->bits = bits; + + return leading_zero_bits; +} + +/***************************************************************************** +Functions exported as part of the bit stream API + *****************************************************************************/ + +/*****************************************************************************/ +void vc_container_bits_init(VC_CONTAINER_BITS_T *bit_stream, + const uint8_t *buffer, + uint32_t available) +{ + vc_container_assert(buffer && (buffer != (const uint8_t *)1)); + + /* Start with buffer pointing at the previous byte with no bits available + * to make the mathematics easier */ + bit_stream->buffer = buffer - 1; + bit_stream->bytes = available; + bit_stream->bits = 0; +} + +/*****************************************************************************/ +uint32_t vc_container_bits_invalidate( VC_CONTAINER_BITS_T *bit_stream ) +{ + bit_stream->buffer = NULL; + return 0; +} + +/*****************************************************************************/ +bool vc_container_bits_valid(VC_CONTAINER_BITS_T *bit_stream) +{ + return (bit_stream->buffer != NULL); +} + +/*****************************************************************************/ +void vc_container_bits_reset(VC_CONTAINER_BITS_T *bit_stream) +{ + bit_stream->bytes = 0; + bit_stream->bits = 0; +} + +/*****************************************************************************/ +const uint8_t *vc_container_bits_current_pointer(const VC_CONTAINER_BITS_T *bit_stream) +{ + const uint8_t *buffer = bit_stream->buffer; + + /* Only valid on byte boundaries, where buffer pointer has not been moved yet */ + vc_container_assert(!bit_stream->bits); + + return buffer ? (buffer + 1) : NULL; +} + +/*****************************************************************************/ +void vc_container_bits_copy_stream(VC_CONTAINER_BITS_T *dst, + const VC_CONTAINER_BITS_T *src) +{ + memcpy(dst, src, sizeof(VC_CONTAINER_BITS_T)); +} + +/*****************************************************************************/ +uint32_t vc_container_bits_available(const VC_CONTAINER_BITS_T *bit_stream) +{ + if (!bit_stream->buffer) + return 0; + return (bit_stream->bytes << 3) + bit_stream->bits; +} + +/*****************************************************************************/ +uint32_t vc_container_bits_bytes_available(const VC_CONTAINER_BITS_T *bit_stream) +{ + if (!bit_stream->buffer) + return 0; + + vc_container_assert(!bit_stream->bits); + + return vc_container_bits_available(bit_stream) >> 3; +} + +/*****************************************************************************/ +void vc_container_bits_skip(VC_CONTAINER_BITS_T *bit_stream, + uint32_t bits_to_skip) +{ + uint32_t have_bits; + uint32_t new_bytes; + + have_bits = vc_container_bits_available(bit_stream); + if (have_bits < bits_to_skip) + { + vc_container_bits_invalidate(bit_stream); + return; + } + + have_bits -= bits_to_skip; + new_bytes = have_bits >> 3; + bit_stream->bits = have_bits & 7; + bit_stream->buffer += (bit_stream->bytes - new_bytes); + bit_stream->bytes = new_bytes; +} + +/*****************************************************************************/ +void vc_container_bits_skip_bytes(VC_CONTAINER_BITS_T *bit_stream, + uint32_t bytes_to_skip) +{ + /* Only valid on byte boundaries */ + vc_container_assert(!bit_stream->bits); + + vc_container_bits_skip(bit_stream, bytes_to_skip << 3); +} + +/*****************************************************************************/ +void vc_container_bits_reduce_bytes(VC_CONTAINER_BITS_T *bit_stream, + uint32_t bytes_to_reduce) +{ + if (bit_stream->bytes >= bytes_to_reduce) + bit_stream->bytes -= bytes_to_reduce; + else + vc_container_bits_invalidate(bit_stream); +} + +/*****************************************************************************/ +void vc_container_bits_copy_bytes(VC_CONTAINER_BITS_T *bit_stream, + uint32_t bytes_to_copy, + uint8_t *dst) +{ + vc_container_assert(!bit_stream->bits); + + if (bit_stream->bytes < bytes_to_copy) + { + /* Not enough data */ + vc_container_bits_invalidate(bit_stream); + return; + } + + /* When the number of bits is zero, the next byte to take is at buffer + 1 */ + memcpy(dst, bit_stream->buffer + 1, bytes_to_copy); + bit_stream->buffer += bytes_to_copy; + bit_stream->bytes -= bytes_to_copy; +} + +/*****************************************************************************/ +uint32_t vc_container_bits_read_u32(VC_CONTAINER_BITS_T *bit_stream, + uint32_t value_bits) +{ + uint32_t value = 0; + uint32_t needed = value_bits; + uint32_t bits; + + vc_container_assert(value_bits <= 32); + + if (needed > vc_container_bits_available(bit_stream)) + return vc_container_bits_invalidate(bit_stream); + + bits = bit_stream->bits; + while (needed) + { + uint32_t take; + + if (!bits) + { + bit_stream->bytes--; + bit_stream->buffer++; + bits = 8; + } + + take = bits; + if (needed < take) take = needed; + + bits -= take; + needed -= take; + + value <<= take; + if (take == 8) + value |= *bit_stream->buffer; /* optimize whole byte case */ + else + value |= (*bit_stream->buffer >> bits) & ((1 << take) - 1); + } + + bit_stream->bits = bits; + return value; +} + +/*****************************************************************************/ +void vc_container_bits_skip_exp_golomb(VC_CONTAINER_BITS_T *bit_stream) +{ + vc_container_bits_skip(bit_stream, vc_container_bits_get_leading_zero_bits(bit_stream)); +} + +/*****************************************************************************/ +uint32_t vc_container_bits_read_u32_exp_golomb(VC_CONTAINER_BITS_T *bit_stream) +{ + uint32_t leading_zero_bits; + uint32_t codeNum; + + leading_zero_bits = vc_container_bits_get_leading_zero_bits(bit_stream); + + /* Anything bigger than 32 bits is definitely overflow */ + if (leading_zero_bits > 32) + return vc_container_bits_invalidate(bit_stream); + + codeNum = vc_container_bits_read_u32(bit_stream, leading_zero_bits); + + if (leading_zero_bits == 32) + { + /* If codeNum is non-zero, it would need 33 bits, so is also overflow */ + if (codeNum) + return vc_container_bits_invalidate(bit_stream); + + return 0xFFFFFFFF; + } + + return codeNum + (1 << leading_zero_bits) - 1; +} + +/*****************************************************************************/ +int32_t vc_container_bits_read_s32_exp_golomb(VC_CONTAINER_BITS_T *bit_stream) +{ + uint32_t uval; + + uval = vc_container_bits_read_u32_exp_golomb(bit_stream); + + /* The signed Exp-Golomb code 0xFFFFFFFF cannot be represented as a signed 32-bit + * integer, because it should be one larger than the largest positive value. */ + if (uval == 0xFFFFFFFF) + return vc_container_bits_invalidate(bit_stream); + + /* Definition of conversion is + * s = ((-1)^(u + 1)) * Ceil(u / 2) + * where '^' is power, but this should be equivalent */ + return ((int32_t)((uval & 1) << 1) - 1) * (int32_t)((uval >> 1) + (uval & 1)); +} + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT + +/*****************************************************************************/ +void vc_container_bits_log(VC_CONTAINER_T *p_ctx, + uint32_t indent, + const char *txt, + VC_CONTAINER_BITS_T *bit_stream, + VC_CONTAINER_BITS_LOG_OP_T op, + uint32_t length) +{ + const char *valid_str = vc_container_bits_valid_str(bit_stream); + const char *indent_str = vc_container_bits_indent_str(indent); + + switch (op) + { + case VC_CONTAINER_BITS_LOG_SKIP: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: %u bits skipped%s", indent_str, txt, length, valid_str); + break; + case VC_CONTAINER_BITS_LOG_SKIP_BYTES: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: %u bytes skipped%s", indent_str, txt, length, valid_str); + break; + case VC_CONTAINER_BITS_LOG_COPY_BYTES: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: %u bytes copied%s", indent_str, txt, length, valid_str); + break; + case VC_CONTAINER_BITS_LOG_REDUCE_BYTES: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: %u bytes reduced%s", indent_str, txt, length, valid_str); + break; + case VC_CONTAINER_BITS_LOG_EG_SKIP: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: Exp-Golomb value skipped%s", indent_str, txt, valid_str); + break; + default: + /* Unexpected operation. Check bit stream logging macros */ + vc_container_assert(0); + } +} + +/*****************************************************************************/ +uint32_t vc_container_bits_log_u32(VC_CONTAINER_T *p_ctx, + uint32_t indent, + const char *txt, + VC_CONTAINER_BITS_T *bit_stream, + VC_CONTAINER_BITS_LOG_OP_T op, + uint32_t length, + uint32_t value) +{ + const char *valid_str = vc_container_bits_valid_str(bit_stream); + const char *indent_str = vc_container_bits_indent_str(indent); + + switch (op) + { + case VC_CONTAINER_BITS_LOG_U8: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: 0x%02x (%u) in %u bits%s", indent_str, txt, value, value, length, valid_str); + break; + case VC_CONTAINER_BITS_LOG_U16: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: 0x%04x (%u) in %u bits%s", indent_str, txt, value, value, length, valid_str); + break; + case VC_CONTAINER_BITS_LOG_U32: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: 0x%08x (%u) in %u bits%s", indent_str, txt, value, value, length, valid_str); + break; + case VC_CONTAINER_BITS_LOG_EG_U32: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: 0x%08x (%u) unsigned Exp-Golomb%s", indent_str, txt, value, value, valid_str); + break; + default: + /* Unexpected operation. Check bit stream logging macros */ + vc_container_assert(0); + } + + return value; +} + +/*****************************************************************************/ +int32_t vc_container_bits_log_s32(VC_CONTAINER_T *p_ctx, + uint32_t indent, + const char *txt, + VC_CONTAINER_BITS_T *bit_stream, + VC_CONTAINER_BITS_LOG_OP_T op, + uint32_t length, + int32_t value) +{ + const char *valid_str = vc_container_bits_valid_str(bit_stream); + const char *indent_str = vc_container_bits_indent_str(indent); + + VC_CONTAINER_PARAM_UNUSED(length); + + switch (op) + { + case VC_CONTAINER_BITS_LOG_EG_S32: + vc_container_log(p_ctx, VC_CONTAINER_LOG_FORMAT, "%s%s: 0x%08x (%d) signed Exp-Golomb%s", indent_str, txt, value, value, valid_str); + break; + default: + /* Unexpected operation. Check bit stream logging macros */ + vc_container_assert(0); + } + + return value; +} + +#endif /* ENABLE_CONTAINERS_LOG_FORMAT */ diff --git a/containers/core/containers_bits.h b/containers/core/containers_bits.h new file mode 100644 index 0000000..10f1cd1 --- /dev/null +++ b/containers/core/containers_bits.h @@ -0,0 +1,344 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VC_CONTAINERS_BITS_H +#define VC_CONTAINERS_BITS_H + +#include "containers/containers.h" + +/** Bit stream structure + * Value are read from the buffer, taking bits from MSB to LSB in sequential + * bytes until the number of bit and the number of bytes runs out. */ +typedef struct vc_container_bits_tag +{ + const uint8_t *buffer; /**< Buffer from which to take bits */ + uint32_t bytes; /**< Number of bytes available from buffer */ + uint32_t bits; /**< Number of bits available at current pointer */ +} VC_CONTAINER_BITS_T; + +/** Initialise a bit stream object. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object to initialise. + * \param buffer Pointer to the start of the byte buffer. + * \param available Number of bytes in the bit stream. + */ +void vc_container_bits_init(VC_CONTAINER_BITS_T *bit_stream, const uint8_t *buffer, uint32_t available); + +/** Invalidates the bit stream. + * Also returns zero, because it allows callers that need to invalidate and + * immediately return zero to do so in a single statement. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return Zero, always. + */ +uint32_t vc_container_bits_invalidate( VC_CONTAINER_BITS_T *bit_stream ); + +/** Returns true if the bit stream is currently valid. + * The stream becomes invalid when a read or skip operation goe beyond the end + * of the stream. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return True if the stream is valid, false if it is invalid. + */ +bool vc_container_bits_valid(VC_CONTAINER_BITS_T *bit_stream); + +/** Reset a valid bit stream object to appear empty. + * Once a stream has become invalid, reset has no effect. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + */ +void vc_container_bits_reset(VC_CONTAINER_BITS_T *bit_stream); + +/** Return the current byte pointer for the bit stream. + * + * \pre bit_stream is not NULL. + * \pre The stream is on a byte boundary. + * + * \param bit_stream The bit stream object. + * \return The current byte pointer, or NULL if the stream is invalid. + */ +const uint8_t *vc_container_bits_current_pointer(const VC_CONTAINER_BITS_T *bit_stream); + +/** Copy one bit stream to another. + * If the source stream is invalid, the destination one will become so as well. + * + * \pre Neither bit stream is NULL. + * + * \param dst The destination bit stream object. + * \param src The source bit stream object. + */ +void vc_container_bits_copy_stream(VC_CONTAINER_BITS_T *dst, const VC_CONTAINER_BITS_T *src); + +/** Return the number of bits left to take from the stream. + * If the stream is invalid, zero is returned. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return The number of bits left to take. + */ +uint32_t vc_container_bits_available(const VC_CONTAINER_BITS_T *bit_stream); + +/** Return the number of bytes left to take from the stream. + * If the stream is invalid, zero is returned. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return The number of bytes left to take. + */ +uint32_t vc_container_bits_bytes_available(const VC_CONTAINER_BITS_T *bit_stream); + +/** Skip past a number of bits in the stream. + * If bits_to_skip is greater than the number of bits available in the stream, + * the stream becomes invalid. + * If the stream is already invalid, this has no effect. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \param bits_to_skip The number of bits to skip. + */ +void vc_container_bits_skip(VC_CONTAINER_BITS_T *bit_stream, uint32_t bits_to_skip); + +/** Skip past a number of bytes in the stream. + * If bytes_to_skip is greater than the number of bytes available in the stream, + * the stream becomes invalid. + * If the stream is already invalid, this has no effect. + * + * \pre bit_stream is not NULL. + * \pre The stream is on a byte boundary. + * + * \param bit_stream The bit stream object. + * \param bytes_to_skip The number of bytes to skip. + */ +void vc_container_bits_skip_bytes(VC_CONTAINER_BITS_T *bit_stream, uint32_t bytes_to_skip); + +/** Reduce the length of the bit stream by a number of bytes. + * This reduces the number of bits/bytes available without changing the current + * position in the stream. If bytes_to_reduce is greater than the number of + * bytes available in the stream, the stream becomes invalid. + * If the stream is already invalid, this has no effect. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \param bytes_to_reduce The number of bytes by which to reduce the stream. + */ +void vc_container_bits_reduce_bytes(VC_CONTAINER_BITS_T *bit_stream, uint32_t bytes_to_reduce); + +/** Copies a number of bytes from the stream to a byte buffer. + * If the stream is or becomes invalid, no data is copied. + * + * \pre bit_stream is not NULL. + * \pre The stream is on a byte boundary. + * + * \param bit_stream The bit stream object. + * \param bytes_to_copy The number of bytes to copy. + * \param dst The byte buffer destination. + */ +void vc_container_bits_copy_bytes(VC_CONTAINER_BITS_T *bit_stream, uint32_t bytes_to_copy, uint8_t *dst); + +/** Returns the next value_bits from the stream. The last bit will be the least + * significant bit in the returned value. + * If value_bits is greater than the number of bits available in the stream, + * the stream becomes invalid. + * If the stream is invalid, or becomes invalid while reading the value, zero + * is returned. + * + * \pre bit_stream is not NULL. + * \pre value_bits is not larger than 32. + * + * \param bit_stream The bit stream object. + * \param value_bits The number of bits to retrieve. + * \return The value read from the stream, or zero if the stream is invalid. + */ +uint32_t vc_container_bits_read_u32(VC_CONTAINER_BITS_T *bit_stream, uint32_t value_bits); + +/** Skips the next Exp-Golomb value in the stream. + * See section 9.1 of ITU-T REC H.264 201003 for details. + * If there are not enough bits in the stream to complete an Exp-Golomb value, + * the stream becomes invalid. + * If the stream is already invalid, this has no effect. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + */ +void vc_container_bits_skip_exp_golomb(VC_CONTAINER_BITS_T *bit_stream); + +/** Returns the next unsigned Exp-Golomb value from the stream. + * See section 9.1 of ITU-T REC H.264 201003 for details. + * If there are not enough bits in the stream to complete an Exp-Golomb value, + * the stream becomes invalid. + * If the next unsigned Exp-Golomb value in the stream is larger than 32 bits, + * or the stream is or becomes invalid, zero is returned. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return The next unsigned value from the stream, or zero on error. + */ +uint32_t vc_container_bits_read_u32_exp_golomb(VC_CONTAINER_BITS_T *bit_stream); + +/** Returns the next signed Exp-Golomb value from the stream. + * See section 9.1.1 of ITU-T REC H.264 201003 for details. + * If there are not enough bits in the stream to complete an Exp-Golomb value, + * the stream becomes invalid. + * If the next signed Exp-Golomb value in the stream is larger than 32 bits, + * or the stream is or becomes invalid, zero is returned. + * + * \pre bit_stream is not NULL. + * + * \param bit_stream The bit stream object. + * \return The next signed value from the stream, or zero on error. + */ +int32_t vc_container_bits_read_s32_exp_golomb(VC_CONTAINER_BITS_T *bit_stream); + +/****************************************************************************** + * Macros reduce function name length and enable logging of some operations * + ******************************************************************************/ +#define BITS_INIT(ctx, bits, buffer, available) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_init(bits, buffer, available)) +#define BITS_INVALIDATE(ctx, bits) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_invalidate(bits)) +#define BITS_VALID(ctx, bits) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_valid(bits)) +#define BITS_RESET(ctx, bits) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_reset(bits)) +#define BITS_AVAILABLE(ctx, bits) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_available(bits)) +#define BITS_BYTES_AVAILABLE(ctx, bits) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_bytes_available(bits)) +#define BITS_CURRENT_POINTER(ctx, bits) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_current_pointer(bits)) +#define BITS_COPY_STREAM(ctx, dst, src) (VC_CONTAINER_PARAM_UNUSED(ctx), vc_container_bits_copy_stream(dst, src)) + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT + +typedef enum { + VC_CONTAINER_BITS_LOG_SKIP, + VC_CONTAINER_BITS_LOG_SKIP_BYTES, + VC_CONTAINER_BITS_LOG_U8, + VC_CONTAINER_BITS_LOG_U16, + VC_CONTAINER_BITS_LOG_U32, + VC_CONTAINER_BITS_LOG_COPY_BYTES, + VC_CONTAINER_BITS_LOG_REDUCE_BYTES, + VC_CONTAINER_BITS_LOG_EG_SKIP, + VC_CONTAINER_BITS_LOG_EG_U32, + VC_CONTAINER_BITS_LOG_EG_S32, +} VC_CONTAINER_BITS_LOG_OP_T; + +/** Logs an operation with void return. + * + * \pre None of p_ctx, txt or bit_stream are NULL. + * + * \param p_ctx Container context. + * \param indent Indent level. + * \param txt Description of what is being read. + * \param bit_stream The bit stream object. + * \param op The operation just performed. + * \param length The length of the operation. + */ +void vc_container_bits_log(VC_CONTAINER_T *p_ctx, uint32_t indent, const char *txt, VC_CONTAINER_BITS_T *bit_stream, VC_CONTAINER_BITS_LOG_OP_T op, uint32_t length); + +/** Logs an operation with unsigned 32-bit integer return. + * + * \pre None of p_ctx, txt or bit_stream are NULL. + * + * \param p_ctx Container context. + * \param indent Indent level. + * \param txt Description of what is being read. + * \param bit_stream The bit stream object. + * \param op The operation just performed. + * \param length The length of the operation. + * \param value The value returned by the operation. + * \return The unsigned 32-bit integer value passed in. + */ +uint32_t vc_container_bits_log_u32(VC_CONTAINER_T *p_ctx, uint32_t indent, const char *txt, VC_CONTAINER_BITS_T *bit_stream, VC_CONTAINER_BITS_LOG_OP_T op, uint32_t length, uint32_t value); + +/** Logs an operation with signed 32-bit integer return. + * + * \pre None of p_ctx, txt or bit_stream are NULL. + * + * \param p_ctx Container context. + * \param indent Indent level. + * \param txt Description of what is being read. + * \param bit_stream The bit stream object. + * \param op The operation just performed. + * \param length The length of the operation. + * \param value The value returned by the operation. + * \return The signed 32-bit integer value passed in. + */ +int32_t vc_container_bits_log_s32(VC_CONTAINER_T *p_ctx, uint32_t indent, const char *txt, VC_CONTAINER_BITS_T *bit_stream, VC_CONTAINER_BITS_LOG_OP_T op, uint32_t length, int32_t value); + +#ifndef BITS_LOG_INDENT +# ifndef CONTAINER_HELPER_LOG_INDENT +# define BITS_LOG_INDENT(ctx) 0 +# else +# define BITS_LOG_INDENT(ctx) ((ctx)->priv->io->module ? CONTAINER_HELPER_LOG_INDENT(a) : 0) +# endif +#endif + +#define BITS_SKIP(ctx, bits, length, txt) (vc_container_bits_skip(bits, length), vc_container_bits_log(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_SKIP, length)) +#define BITS_SKIP_BYTES(ctx, bits, length, txt) (vc_container_bits_skip_bytes(bits, length), vc_container_bits_log(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_SKIP_BYTES, length)) + +#define BITS_READ_U8(ctx, bits, length, txt) (uint8_t)vc_container_bits_log_u32(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_U8, length, vc_container_bits_read_u32(bits, length)) +#define BITS_READ_U16(ctx, bits, length, txt) (uint16_t)vc_container_bits_log_u32(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_U16, length, vc_container_bits_read_u32(bits, length)) +#define BITS_READ_U32(ctx, bits, length, txt) vc_container_bits_log_u32(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_U32, length, vc_container_bits_read_u32(bits, length)) + +#define BITS_COPY_BYTES(ctx, bits, length, dst, txt) (vc_container_bits_copy_bytes(bits, length, dst), vc_container_bits_log(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_COPY_BYTES, length)) + +#define BITS_REDUCE_BYTES(ctx, bits, length, txt) (vc_container_bits_reduce_bytes(bits, length), vc_container_bits_log(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_REDUCE_BYTES, length)) + +#define BITS_SKIP_EXP(ctx, bits, txt) (vc_container_bits_skip_exp_golomb(bits), vc_container_bits_log(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_EG_SKIP, 0)) + +#define BITS_READ_S32_EXP(ctx, bits, txt) vc_container_bits_log_s32(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_EG_S32, 0, vc_container_bits_read_s32_exp_golomb(bits)) +#define BITS_READ_U32_EXP(ctx, bits, txt) vc_container_bits_log_u32(ctx, BITS_LOG_INDENT(ctx), txt, bits, VC_CONTAINER_BITS_LOG_EG_U32, 0, vc_container_bits_read_u32_exp_golomb(bits)) + +#else /* ENABLE_CONTAINERS_LOG_FORMAT */ + +#define BITS_SKIP(ctx, bits, length, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_skip(bits, length)) +#define BITS_SKIP_BYTES(ctx, bits, length, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_skip_bytes(bits, length)) + +#define BITS_READ_U8(ctx, bits, length, txt) (uint8_t)(VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_read_u32(bits, length)) +#define BITS_READ_U16(ctx, bits, length, txt) (uint16_t)(VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_read_u32(bits, length)) +#define BITS_READ_U32(ctx, bits, length, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_read_u32(bits, length)) + +#define BITS_COPY_BYTES(ctx, bits, length, dst, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_copy_bytes(bits, length, dst)) + +#define BITS_REDUCE_BYTES(ctx, bits, length, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_reduce_bytes(bits, length)) + +#define BITS_SKIP_EXP(ctx, bits, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_skip_exp_golomb(bits)) + +#define BITS_READ_S32_EXP(ctx, bits, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_read_s32_exp_golomb(bits)) +#define BITS_READ_U32_EXP(ctx, bits, txt) (VC_CONTAINER_PARAM_UNUSED(ctx), VC_CONTAINER_PARAM_UNUSED(txt), vc_container_bits_read_u32_exp_golomb(bits)) + +#endif /* ENABLE_CONTAINERS_LOG_FORMAT */ + +#endif /* VC_CONTAINERS_BITS_H */ diff --git a/containers/core/containers_bytestream.h b/containers/core/containers_bytestream.h new file mode 100644 index 0000000..1f20863 --- /dev/null +++ b/containers/core/containers_bytestream.h @@ -0,0 +1,389 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_BYTESTREAM_H +#define VC_CONTAINERS_BYTESTREAM_H + +/** \file + * Utility functions to provide a byte stream out of a list of container packets + */ + +typedef struct VC_CONTAINER_BYTESTREAM_T +{ + VC_CONTAINER_PACKET_T *first; /**< first packet in the chain */ + VC_CONTAINER_PACKET_T **last; /**< last packet in the chain */ + + VC_CONTAINER_PACKET_T *current; /**< packet containing the current read pointer */ + size_t current_offset; /**< position of current packet (in bytes) */ + size_t offset; /**< position within the current packet */ + + size_t bytes; /**< Number of bytes available in the bytestream */ + +} VC_CONTAINER_BYTESTREAM_T; + +/*****************************************************************************/ +STATIC_INLINE void bytestream_init( VC_CONTAINER_BYTESTREAM_T *stream ) +{ + stream->first = stream->current = NULL; + stream->last = &stream->first; + stream->offset = stream->current_offset = stream->bytes = 0; +} + +STATIC_INLINE void bytestream_push( VC_CONTAINER_BYTESTREAM_T *stream, + VC_CONTAINER_PACKET_T *packet ) +{ + *stream->last = packet; + stream->last = &packet->next; + packet->next = NULL; + if( !stream->current ) stream->current = packet; + stream->bytes += packet->size; +} + +STATIC_INLINE VC_CONTAINER_PACKET_T *bytestream_pop( VC_CONTAINER_BYTESTREAM_T *stream ) +{ + VC_CONTAINER_PACKET_T *packet = stream->first; + + if( stream->current == packet ) + return NULL; + vc_container_assert(packet); + + stream->bytes -= packet->size; + stream->current_offset -= packet->size; + stream->first = packet->next; + if( !stream->first ) + stream->last = &stream->first; + return packet; +} + +STATIC_INLINE VC_CONTAINER_PACKET_T *bytestream_get_packet( VC_CONTAINER_BYTESTREAM_T *stream, size_t *offset ) +{ + VC_CONTAINER_PACKET_T *packet = stream->current; + size_t off = stream->offset; + + while(packet && packet->size == off) + { + packet = packet->next; + off = 0; + } + + if (offset) + *offset = off; + + return packet; +} + +STATIC_INLINE bool bytestream_skip_packet( VC_CONTAINER_BYTESTREAM_T *stream ) +{ + VC_CONTAINER_PACKET_T *packet = stream->current; + + if( packet ) + { + stream->current = packet->next; + stream->current_offset += (packet->size - stream->offset); + stream->offset = 0; + } + + return !!packet; +} + +STATIC_INLINE void bytestream_get_timestamps( VC_CONTAINER_BYTESTREAM_T *stream, int64_t *pts, int64_t *dts, bool b_same ) +{ + VC_CONTAINER_PACKET_T *packet = bytestream_get_packet( stream, 0 ); + + if(packet) + { + if(b_same && packet->pts == VC_CONTAINER_TIME_UNKNOWN) packet->pts = packet->dts; + if(pts) *pts = packet->pts; + if(dts) *dts = packet->dts; + + packet->pts = packet->dts = VC_CONTAINER_TIME_UNKNOWN; + return; + } + + if(pts) *pts = VC_CONTAINER_TIME_UNKNOWN; + if(dts) *dts = VC_CONTAINER_TIME_UNKNOWN; +} + +STATIC_INLINE void bytestream_get_timestamps_and_offset( VC_CONTAINER_BYTESTREAM_T *stream, + int64_t *pts, int64_t *dts, size_t *offset, bool b_same ) +{ + VC_CONTAINER_PACKET_T *packet = bytestream_get_packet( stream, offset ); + + if(packet) + { + if(b_same && packet->pts == VC_CONTAINER_TIME_UNKNOWN) packet->pts = packet->dts; + if(pts) *pts = packet->pts; + if(dts) *dts = packet->dts; + + packet->pts = packet->dts = VC_CONTAINER_TIME_UNKNOWN; + return; + } + + if(pts) *pts = VC_CONTAINER_TIME_UNKNOWN; + if(dts) *dts = VC_CONTAINER_TIME_UNKNOWN; +} + +STATIC_INLINE size_t bytestream_size( VC_CONTAINER_BYTESTREAM_T *stream ) +{ + return stream->bytes - stream->current_offset - stream->offset; +} + +STATIC_INLINE VC_CONTAINER_STATUS_T bytestream_skip( VC_CONTAINER_BYTESTREAM_T *stream, size_t size ) +{ + VC_CONTAINER_PACKET_T *packet; + size_t offset, bytes = 0, skip; + + if( !size ) + return VC_CONTAINER_SUCCESS; /* Nothing to do */ + if( stream->bytes - stream->current_offset - stream->offset < size ) + return VC_CONTAINER_ERROR_EOS; /* Not enough data */ + + for( packet = stream->current, offset = stream->offset; ; + packet = packet->next, offset = 0 ) + { + if( packet->size - offset >= size) + break; + + skip = packet->size - offset; + bytes += skip; + size -= skip; + } + + stream->current = packet; + stream->current_offset += stream->offset - offset + bytes; + stream->offset = offset + size; + return VC_CONTAINER_SUCCESS; +} + +STATIC_INLINE VC_CONTAINER_STATUS_T bytestream_get( VC_CONTAINER_BYTESTREAM_T *stream, uint8_t *data, size_t size ) +{ + VC_CONTAINER_PACKET_T *packet; + size_t offset, bytes = 0, copy; + + if( !size ) + return VC_CONTAINER_SUCCESS; /* Nothing to do */ + if( stream->bytes - stream->current_offset - stream->offset < size ) + return VC_CONTAINER_ERROR_EOS; /* Not enough data */ + + for( packet = stream->current, offset = stream->offset; ; + packet = packet->next, offset = 0 ) + { + if( packet->size - offset >= size) + break; + + copy = packet->size - offset; + memcpy( data, packet->data + offset, copy ); + bytes += copy; + data += copy; + size -= copy; + } + + memcpy( data, packet->data + offset, size ); + stream->current = packet; + stream->current_offset += stream->offset - offset + bytes; + stream->offset = offset + size; + return VC_CONTAINER_SUCCESS; +} + +STATIC_INLINE VC_CONTAINER_STATUS_T bytestream_peek( VC_CONTAINER_BYTESTREAM_T *stream, uint8_t *data, size_t size ) +{ + VC_CONTAINER_PACKET_T *packet; + size_t offset, copy; + + if( !size ) + return VC_CONTAINER_SUCCESS; /* Nothing to do */ + if( stream->bytes - stream->current_offset - stream->offset < size ) + return VC_CONTAINER_ERROR_EOS; /* Not enough data */ + + for( packet = stream->current, offset = stream->offset; ; + packet = packet->next, offset = 0 ) + { + if( packet->size - offset >= size) + break; + + copy = packet->size - offset; + memcpy( data, packet->data + offset, copy ); + data += copy; + size -= copy; + } + + memcpy( data, packet->data + offset, size ); + return VC_CONTAINER_SUCCESS; +} + +STATIC_INLINE VC_CONTAINER_STATUS_T bytestream_peek_at( VC_CONTAINER_BYTESTREAM_T *stream, + size_t peek_offset, uint8_t *data, size_t size ) +{ + VC_CONTAINER_PACKET_T *packet; + size_t copy; + + if( !size ) + return VC_CONTAINER_SUCCESS; /* Nothing to do */ + if( stream->bytes - stream->current_offset - stream->offset < peek_offset + size ) + return VC_CONTAINER_ERROR_EOS; /* Not enough data */ + + peek_offset += stream->offset; + + /* Find the right place */ + for( packet = stream->current; ; packet = packet->next ) + { + if( packet->size > peek_offset ) + break; + + peek_offset -= packet->size; + } + + /* Copy the data */ + for( ; ; packet = packet->next, peek_offset = 0 ) + { + if( packet->size - peek_offset >= size) + break; + + copy = packet->size - peek_offset; + memcpy( data, packet->data + peek_offset, copy ); + data += copy; + size -= copy; + } + + memcpy( data, packet->data + peek_offset, size ); + return VC_CONTAINER_SUCCESS; +} + +STATIC_INLINE VC_CONTAINER_STATUS_T bytestream_skip_byte( VC_CONTAINER_BYTESTREAM_T *stream ) +{ + VC_CONTAINER_PACKET_T *packet = stream->current; + + if( !packet ) + return VC_CONTAINER_ERROR_EOS; + + /* Fast path first */ + if( packet->size - stream->offset ) + { + stream->offset++; + return VC_CONTAINER_SUCCESS; + } + + return bytestream_skip( stream, 1 ); +} + +STATIC_INLINE VC_CONTAINER_STATUS_T packet_peek_byte( VC_CONTAINER_BYTESTREAM_T *stream, + uint8_t *data ) +{ + VC_CONTAINER_PACKET_T *packet = stream->current; + + if( !packet ) + return VC_CONTAINER_ERROR_EOS; + + /* Fast path first */ + if( packet->size - stream->offset ) + { + *data = packet->data[stream->offset]; + return VC_CONTAINER_SUCCESS; + } + + return bytestream_peek( stream, data, 1 ); +} + +STATIC_INLINE VC_CONTAINER_STATUS_T packet_get_byte( VC_CONTAINER_BYTESTREAM_T *stream, + uint8_t *data ) +{ + VC_CONTAINER_PACKET_T *packet = stream->current; + + if( !packet ) + return VC_CONTAINER_ERROR_EOS; + + /* Fast path first */ + if( packet->size - stream->offset ) + { + *data = packet->data[stream->offset]; + stream->offset++; + return VC_CONTAINER_SUCCESS; + } + + return bytestream_get( stream, data, 1 ); +} + +STATIC_INLINE VC_CONTAINER_STATUS_T bytestream_find_startcode( VC_CONTAINER_BYTESTREAM_T *stream, + size_t *search_offset, const uint8_t *startcode, unsigned int length ) +{ + VC_CONTAINER_PACKET_T *packet, *backup_packet; + size_t position, start_offset = position = *search_offset; + size_t offset, backup_offset; + unsigned int match = 0; + + if( stream->bytes - stream->current_offset - stream->offset < start_offset + length ) + return VC_CONTAINER_ERROR_EOS; /* Not enough data */ + + /* Find the right place */ + for( packet = stream->current, offset = stream->offset; + packet != NULL; packet = packet->next, offset = 0 ) + { + if( packet->size - offset > start_offset) + break; + + start_offset -= (packet->size - offset); + } + + /* Start the search for the start code. + * To make things simple we try to find a match one byte at a time. */ + for( offset += start_offset; + packet != NULL; packet = packet->next, offset = 0 ) + { + for( ; offset < packet->size; offset++ ) + { + if( packet->data[offset] != startcode[match] ) + { + if ( match ) /* False positive */ + { + packet = backup_packet; + offset = backup_offset; + match = 0; + } + position++; + continue; + } + + /* We've got a match */ + if( !match++ ) + { + backup_packet = packet; + backup_offset = offset; + } + + if( match == length ) + { + /* We have the full start code it */ + *search_offset = position; + return VC_CONTAINER_SUCCESS; + } + } + } + + *search_offset = position; + return VC_CONTAINER_ERROR_EOS; /* No luck in finding the start code */ +} + +#endif /* VC_CONTAINERS_BYTESTREAM_H */ diff --git a/containers/core/containers_codecs.c b/containers/core/containers_codecs.c new file mode 100644 index 0000000..16e1593 --- /dev/null +++ b/containers/core/containers_codecs.c @@ -0,0 +1,209 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/containers.h" +#include "containers/containers_codecs.h" +#include "containers/core/containers_utils.h" + +/*****************************************************************************/ +static struct { + VC_CONTAINER_FOURCC_T codec; + uint16_t id; +} codec_to_wf_table[] = +{ + {VC_CONTAINER_CODEC_PCM_SIGNED_LE, WAVE_FORMAT_PCM}, + {VC_CONTAINER_CODEC_ALAW, WAVE_FORMAT_ALAW}, + {VC_CONTAINER_CODEC_MULAW, WAVE_FORMAT_MULAW}, + {VC_CONTAINER_CODEC_ADPCM_MS, WAVE_FORMAT_ADPCM}, + {VC_CONTAINER_CODEC_MPGA, WAVE_FORMAT_MPEG}, + {VC_CONTAINER_CODEC_MPGA, WAVE_FORMAT_MPEGLAYER3}, + {VC_CONTAINER_CODEC_WMA1, WAVE_FORMAT_WMAUDIO1}, + {VC_CONTAINER_CODEC_WMA2, WAVE_FORMAT_WMAUDIO2}, + {VC_CONTAINER_CODEC_WMAP, WAVE_FORMAT_WMAUDIOPRO}, + {VC_CONTAINER_CODEC_WMAL, WAVE_FORMAT_WMAUDIO_LOSSLESS}, + {VC_CONTAINER_CODEC_WMAV, WAVE_FORMAT_WMAUDIO_VOICE}, + {VC_CONTAINER_CODEC_AC3, WAVE_FORMAT_DVM}, + {VC_CONTAINER_CODEC_AC3, WAVE_FORMAT_DOLBY_AC3_SPDIF}, /**< AC-3 padded for S/PDIF */ + {VC_CONTAINER_CODEC_AC3, WAVE_FORMAT_RAW_SPORT}, /**< AC-3 padded for S/PDIF */ + {VC_CONTAINER_CODEC_AC3, WAVE_FORMAT_ESST_AC3}, /**< AC-3 padded for S/PDIF */ + {VC_CONTAINER_CODEC_EAC3, WAVE_FORMAT_DVM}, + {VC_CONTAINER_CODEC_DTS, WAVE_FORMAT_DTS}, +#if 0 + {CODEC_G726, WAVE_FORMAT_G726_ADPCM}, + {CODEC_G726, WAVE_FORMAT_DF_G726}, + {CODEC_G726, WAVE_FORMAT_G726ADPCM}, + {CODEC_G726, WAVE_FORMAT_PANASONIC_G726}, +#endif + {VC_CONTAINER_CODEC_MP4A, WAVE_FORMAT_AAC}, + {VC_CONTAINER_CODEC_MP4A, WAVE_FORMAT_MP4A}, + {VC_CONTAINER_CODEC_ATRAC3, WAVE_FORMAT_SONY_SCX}, + {VC_CONTAINER_CODEC_UNKNOWN, WAVE_FORMAT_UNKNOWN} +}; + +VC_CONTAINER_FOURCC_T waveformat_to_codec(uint16_t waveformat_id) +{ + unsigned int i; + for(i = 0; codec_to_wf_table[i].codec != VC_CONTAINER_CODEC_UNKNOWN; i++) + if(codec_to_wf_table[i].id == waveformat_id) break; + return codec_to_wf_table[i].codec; +} + +uint16_t codec_to_waveformat(VC_CONTAINER_FOURCC_T codec) +{ + unsigned int i; + for(i = 0; codec_to_wf_table[i].codec != VC_CONTAINER_CODEC_UNKNOWN; i++) + if(codec_to_wf_table[i].codec == codec) break; + return codec_to_wf_table[i].id; +} + +static struct { + VC_CONTAINER_FOURCC_T codec; + uint32_t fourcc; +} codec_to_vfw_table[] = +{ +#if defined(ENABLE_CONTAINERS_STANDALONE) || !defined(NDEBUG) + /* We are legally required to not play DivX in RELEASE mode. See Jira SW-3138 */ + {VC_CONTAINER_CODEC_DIV3, VC_FOURCC('D','I','V','3')}, + {VC_CONTAINER_CODEC_DIV3, VC_FOURCC('d','i','v','3')}, + {VC_CONTAINER_CODEC_DIV4, VC_FOURCC('D','I','V','4')}, + {VC_CONTAINER_CODEC_DIV4, VC_FOURCC('d','i','v','4')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('D','X','5','0')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('D','I','V','X')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('d','i','v','x')}, +#endif /* ENABLE_CONTAINERS_STANDALONE || !NDEBUG */ + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('M','P','4','V')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('m','p','4','v')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('M','P','4','S')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('m','p','4','s')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('M','4','S','2')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('m','4','s','2')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('F','M','P','4')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('X','V','I','D')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('x','v','i','d')}, + {VC_CONTAINER_CODEC_DIV3, VC_FOURCC('M','P','4','3')}, + {VC_CONTAINER_CODEC_DIV3, VC_FOURCC('m','p','4','3')}, + {VC_CONTAINER_CODEC_MP1V, VC_FOURCC('m','p','g','1')}, + {VC_CONTAINER_CODEC_MP1V, VC_FOURCC('M','P','G','1')}, + {VC_CONTAINER_CODEC_MP2V, VC_FOURCC('m','p','g','2')}, + {VC_CONTAINER_CODEC_MP2V, VC_FOURCC('M','P','G','2')}, + {VC_CONTAINER_CODEC_MJPEG, VC_FOURCC('M','J','P','G')}, + {VC_CONTAINER_CODEC_MJPEG, VC_FOURCC('m','j','p','g')}, + {VC_CONTAINER_CODEC_WMV1, VC_FOURCC('W','M','V','1')}, + {VC_CONTAINER_CODEC_WMV1, VC_FOURCC('w','m','v','1')}, + {VC_CONTAINER_CODEC_WMV2, VC_FOURCC('W','M','V','2')}, + {VC_CONTAINER_CODEC_WMV2, VC_FOURCC('w','m','v','2')}, + {VC_CONTAINER_CODEC_WMV3, VC_FOURCC('W','M','V','3')}, + {VC_CONTAINER_CODEC_WMV3, VC_FOURCC('w','m','v','3')}, + {VC_CONTAINER_CODEC_WVC1, VC_FOURCC('W','V','C','1')}, + {VC_CONTAINER_CODEC_WVC1, VC_FOURCC('w','v','c','1')}, + {VC_CONTAINER_CODEC_WMVA, VC_FOURCC('w','m','v','a')}, + {VC_CONTAINER_CODEC_WMVA, VC_FOURCC('W','M','V','A')}, + {VC_CONTAINER_CODEC_VP6, VC_FOURCC('V','P','6','F')}, + {VC_CONTAINER_CODEC_VP6, VC_FOURCC('v','p','6','f')}, + {VC_CONTAINER_CODEC_VP7, VC_FOURCC('V','P','7','0')}, + {VC_CONTAINER_CODEC_VP7, VC_FOURCC('v','p','7','0')}, + {VC_CONTAINER_CODEC_H263, VC_FOURCC('H','2','6','3')}, + {VC_CONTAINER_CODEC_H263, VC_FOURCC('h','2','6','3')}, + {VC_CONTAINER_CODEC_H264, VC_FOURCC('H','2','6','4')}, + {VC_CONTAINER_CODEC_H264, VC_FOURCC('h','2','6','4')}, + {VC_CONTAINER_CODEC_H264, VC_FOURCC('A','V','C','1')}, + {VC_CONTAINER_CODEC_H264, VC_FOURCC('a','v','c','1')}, + {VC_CONTAINER_CODEC_SPARK, VC_FOURCC('F','L','V','1')}, + {VC_CONTAINER_CODEC_SPARK, VC_FOURCC('f','l','v','1')}, + {VC_CONTAINER_CODEC_UNKNOWN, 0} +}; + +VC_CONTAINER_FOURCC_T vfw_fourcc_to_codec(uint32_t fourcc) +{ + unsigned int i; + for(i = 0; codec_to_vfw_table[i].codec != VC_CONTAINER_CODEC_UNKNOWN; i++) + if(codec_to_vfw_table[i].fourcc == fourcc) break; + + if(codec_to_vfw_table[i].codec == VC_CONTAINER_CODEC_UNKNOWN) + return fourcc; + + return codec_to_vfw_table[i].codec; +} + +uint32_t codec_to_vfw_fourcc(VC_CONTAINER_FOURCC_T codec) +{ + unsigned int i; + for(i = 0; codec_to_vfw_table[i].codec != VC_CONTAINER_CODEC_UNKNOWN; i++) + if(codec_to_vfw_table[i].codec == codec) break; + + return codec_to_vfw_table[i].fourcc; +} + +static struct { + VC_CONTAINER_FOURCC_T codec; + uint32_t fourcc; +} codec_to_fourcc_table[] = +{ + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('M','P','4','S')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('M','4','S','2')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('m','p','4','s')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('m','4','s','2')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('M','P','4','V')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('m','p','4','v')}, + {VC_CONTAINER_CODEC_MP4V, VC_FOURCC('F','M','P','4')}, + {VC_CONTAINER_CODEC_DIV3, VC_FOURCC('M','P','4','3')}, + {VC_CONTAINER_CODEC_DIV3, VC_FOURCC('m','p','4','3')}, + {VC_CONTAINER_CODEC_WMV1, VC_FOURCC('W','M','V','1')}, + {VC_CONTAINER_CODEC_WMV1, VC_FOURCC('w','m','v','1')}, + {VC_CONTAINER_CODEC_WMV2, VC_FOURCC('W','M','V','2')}, + {VC_CONTAINER_CODEC_WMV2, VC_FOURCC('w','m','v','2')}, + {VC_CONTAINER_CODEC_WMV3, VC_FOURCC('W','M','V','3')}, + {VC_CONTAINER_CODEC_WMV3, VC_FOURCC('w','m','v','3')}, + {VC_CONTAINER_CODEC_MP1V, VC_FOURCC('m','p','g','1')}, + {VC_CONTAINER_CODEC_MP1V, VC_FOURCC('M','P','G','1')}, + {VC_CONTAINER_CODEC_MP2V, VC_FOURCC('m','p','g','2')}, + {VC_CONTAINER_CODEC_MP2V, VC_FOURCC('M','P','G','2')}, + {VC_CONTAINER_CODEC_MJPEG, VC_FOURCC('M','J','P','G')}, + {VC_CONTAINER_CODEC_MJPEG, VC_FOURCC('m','j','p','g')}, + {VC_CONTAINER_CODEC_UNKNOWN, 0} +}; + +VC_CONTAINER_FOURCC_T fourcc_to_codec(uint32_t fourcc) +{ + unsigned int i; + for(i = 0; codec_to_fourcc_table[i].codec != VC_CONTAINER_CODEC_UNKNOWN; i++) + if(codec_to_fourcc_table[i].fourcc == fourcc) break; + + return codec_to_fourcc_table[i].codec; +} + +uint32_t codec_to_fourcc(VC_CONTAINER_FOURCC_T codec) +{ + unsigned int i; + for(i = 0; codec_to_fourcc_table[i].codec != VC_CONTAINER_CODEC_UNKNOWN; i++) + if(codec_to_fourcc_table[i].codec == codec) break; + + return codec_to_fourcc_table[i].fourcc; +} diff --git a/containers/core/containers_common.h b/containers/core/containers_common.h new file mode 100644 index 0000000..eb18671 --- /dev/null +++ b/containers/core/containers_common.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_COMMON_H +#define VC_CONTAINERS_COMMON_H + +/** \file containers_common.h + * Common definitions for containers infrastructure + */ + +#ifndef ENABLE_CONTAINERS_STANDALONE +# include "vcos.h" +# define vc_container_assert(a) vcos_assert(a) +#else +# include "assert.h" +# define vc_container_assert(a) assert(a) +#endif /* ENABLE_CONTAINERS_STANDALONE */ + +#ifndef countof +# define countof(a) (sizeof(a) / sizeof(a[0])) +#endif + +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifdef _MSC_VER +# define strcasecmp stricmp +# define strncasecmp strnicmp +#endif + +#define STATIC_INLINE static __inline +#define VC_CONTAINER_PARAM_UNUSED(a) (void)(a) + +#if defined(__HIGHC__) && !defined(strcasecmp) +# define strcasecmp(a,b) _stricmp(a,b) +#endif + +#if defined(__GNUC__) && (__GNUC__ > 2) +# define VC_CONTAINER_CONSTRUCTOR(func) void __attribute__((constructor,used)) func(void) +# define VC_CONTAINER_DESTRUCTOR(func) void __attribute__((destructor,used)) func(void) +#else +# define VC_CONTAINER_CONSTRUCTOR(func) void func(void) +# define VC_CONTAINER_DESTRUCTOR(func) void func(void) +#endif + +#endif /* VC_CONTAINERS_COMMON_H */ diff --git a/containers/core/containers_filters.c b/containers/core/containers_filters.c new file mode 100644 index 0000000..a5bc0b7 --- /dev/null +++ b/containers/core/containers_filters.c @@ -0,0 +1,222 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_private.h" +#include "containers/core/containers_filters.h" + +#if !defined(ENABLE_CONTAINERS_STANDALONE) + #include "vcos_dlfcn.h" + #define DL_SUFFIX VCOS_SO_EXT + #ifndef DL_PATH_PREFIX + #define DL_PATH_PREFIX "" + #endif +#endif + +typedef struct VC_CONTAINER_FILTER_PRIVATE_T +{ + /** Pointer to the container filter code and symbols */ + void *handle; +} VC_CONTAINER_FILTER_PRIVATE_T; + +typedef VC_CONTAINER_STATUS_T (*VC_CONTAINER_FILTER_OPEN_FUNC_T)(VC_CONTAINER_FILTER_T*, VC_CONTAINER_FOURCC_T); + +static VC_CONTAINER_FILTER_OPEN_FUNC_T load_library(void **handle, VC_CONTAINER_FOURCC_T filter, const char *name); +static void unload_library(void *handle); + +static struct { + VC_CONTAINER_FOURCC_T filter; + const char *name; +} filter_to_name_table[] = +{ + {VC_FOURCC('d','r','m',' '), "divx"}, + {VC_FOURCC('d','r','m',' '), "hdcp2"}, + {0, NULL} +}; + +static VC_CONTAINER_STATUS_T vc_container_filter_load(VC_CONTAINER_FILTER_T *p_ctx, + VC_CONTAINER_FOURCC_T filter, + VC_CONTAINER_FOURCC_T type) +{ + void *handle = NULL; + VC_CONTAINER_FILTER_OPEN_FUNC_T func; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + unsigned int i; + + for(i = 0; filter_to_name_table[i].filter; ++i) + { + if (filter_to_name_table[i].filter == filter) + { + if ((func = load_library(&handle, filter, filter_to_name_table[i].name)) != NULL) + { + status = (*func)(p_ctx, type); + if(status == VC_CONTAINER_SUCCESS) break; + unload_library(handle); + if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) break; + } + } + } + + p_ctx->priv->handle = handle; + return status; +} + +static void vc_container_filter_unload(VC_CONTAINER_FILTER_T *p_ctx) +{ + unload_library(p_ctx->priv->handle); + p_ctx->priv->handle = NULL; +} + +/*****************************************************************************/ +VC_CONTAINER_FILTER_T *vc_container_filter_open(VC_CONTAINER_FOURCC_T filter, + VC_CONTAINER_FOURCC_T type, + VC_CONTAINER_T *p_container, + VC_CONTAINER_STATUS_T *p_status ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND; + VC_CONTAINER_FILTER_T *p_ctx = 0; + VC_CONTAINER_FILTER_PRIVATE_T *priv = 0; + + /* Allocate our context before trying out the different filter modules */ + p_ctx = malloc(sizeof(*p_ctx) + sizeof(*priv)); + if(!p_ctx) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(p_ctx, 0, sizeof(*p_ctx) + sizeof(*priv)); + p_ctx->priv = priv = (VC_CONTAINER_FILTER_PRIVATE_T *)&p_ctx[1]; + p_ctx->container = p_container; + + status = vc_container_filter_load(p_ctx, filter, type); + if(status != VC_CONTAINER_SUCCESS) goto error; + + end: + if(p_status) *p_status = status; + return p_ctx; + + error: + if(p_ctx) free(p_ctx); + p_ctx = 0; + goto end; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_filter_close( VC_CONTAINER_FILTER_T *p_ctx ) +{ + if (p_ctx) + { + if(p_ctx->pf_close) p_ctx->pf_close(p_ctx); + if(p_ctx->priv && p_ctx->priv->handle) vc_container_filter_unload(p_ctx); + free(p_ctx); + } + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_filter_process( VC_CONTAINER_FILTER_T *p_ctx, VC_CONTAINER_PACKET_T *p_packet ) +{ + VC_CONTAINER_STATUS_T status; + status = p_ctx->pf_process(p_ctx, p_packet); + return status; +} + +VC_CONTAINER_STATUS_T vc_container_filter_control(VC_CONTAINER_FILTER_T *p_ctx, VC_CONTAINER_CONTROL_T operation, ... ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + va_list args; + + va_start( args, operation ); + if(p_ctx->pf_control) + status = p_ctx->pf_control(p_ctx, operation, args); + va_end( args ); + + return status; +} + +static VC_CONTAINER_FILTER_OPEN_FUNC_T load_library(void **handle, VC_CONTAINER_FOURCC_T filter, const char *name) +{ + VC_CONTAINER_FILTER_OPEN_FUNC_T func = NULL; +#ifdef ENABLE_CONTAINERS_STANDALONE + VC_CONTAINER_PARAM_UNUSED(handle); + VC_CONTAINER_PARAM_UNUSED(filter); + VC_CONTAINER_PARAM_UNUSED(name); +#else + char *dl_name, *entrypt_name; + const char *entrypt_name_short = "filter_open"; + char filter_[6], *ptr; + void *dl_handle; + int dl_name_len; + int entrypt_name_len; + + snprintf(filter_, sizeof(filter_), "%4.4s", (const char*)&filter); + ptr = strchr(filter_, '\0'); + while (ptr > filter_ && isspace(*--ptr)) *ptr = '\0'; + strncat(filter_, "_", 1); + + dl_name_len = strlen(DL_PATH_PREFIX) + strlen(filter_) + strlen(name) + strlen(DL_SUFFIX) + 1; + dl_name = malloc(dl_name_len); + if (!dl_name) return NULL; + + entrypt_name_len = strlen(name) + 1 + strlen(filter_) + strlen(entrypt_name_short) + 1; + entrypt_name = malloc(entrypt_name_len); + if (!entrypt_name) + { + free(dl_name); + return NULL; + } + + snprintf(dl_name, dl_name_len, "%s%s%s%s", DL_PATH_PREFIX, filter_, name, DL_SUFFIX); + snprintf(entrypt_name, entrypt_name_len, "%s_%s%s", name, filter_, entrypt_name_short); + + if ((dl_handle = vcos_dlopen(dl_name, VCOS_DL_NOW)) != NULL) + { + /* Try generic entrypoint name before the mangled, full name */ + func = (VC_CONTAINER_FILTER_OPEN_FUNC_T)vcos_dlsym(dl_handle, entrypt_name_short); + if (!func) func = (VC_CONTAINER_FILTER_OPEN_FUNC_T)vcos_dlsym(dl_handle, entrypt_name); + /* Only return handle if symbol found */ + if (func) + *handle = dl_handle; + else + vcos_dlclose(dl_handle); + } + + free(dl_name); + free(entrypt_name); +#endif + return func; +} + +static void unload_library(void *handle) +{ +#ifdef ENABLE_CONTAINERS_STANDALONE + VC_CONTAINER_PARAM_UNUSED(handle); +#else + vcos_dlclose(handle); +#endif +} diff --git a/containers/core/containers_filters.h b/containers/core/containers_filters.h new file mode 100644 index 0000000..9026274 --- /dev/null +++ b/containers/core/containers_filters.h @@ -0,0 +1,110 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_FILTERS_H +#define VC_CONTAINERS_FILTERS_H + +/** \file containers_filters.h + * Interface definition for the filter abstraction used by the container + * common layer */ +#include + +#include "containers/containers.h" + +/** \defgroup VcContainerFilterApi Container Filter API */ +/* @{ */ + +/** Container Filter Context. + * This structure defines the context for a container filter instance */ +typedef struct VC_CONTAINER_FILTER_T +{ + /** Pointer to container instance */ + struct VC_CONTAINER_T *container; + /** Pointer to information private to the container filter instance */ + struct VC_CONTAINER_FILTER_PRIVATE_T *priv; + /** Pointer to information private to the container filter module */ + struct VC_CONTAINER_FILTER_MODULE_T *module; + + /** \note the following list of function pointers should not be used directly. + * They defines the interface for implementing container filter modules and are + * filled in by the container filter modules themselves. */ + + /** \private + * Function pointer to close and free all resources allocated by a + * container filter module */ + VC_CONTAINER_STATUS_T (*pf_close)(struct VC_CONTAINER_FILTER_T *filter); + + /** \private + * Function pointer to filter a data packet using a container filter module */ + VC_CONTAINER_STATUS_T (*pf_process)(struct VC_CONTAINER_FILTER_T *filter, VC_CONTAINER_PACKET_T *p_packet); + + /** \private + * Function pointer to control container filter module */ + VC_CONTAINER_STATUS_T (*pf_control)( struct VC_CONTAINER_FILTER_T *filter, VC_CONTAINER_CONTROL_T operation, va_list args ); + +} VC_CONTAINER_FILTER_T; + +/** Opens a container filter using a four character code describing the filter. + * This will create an instance of the container filter. + * + * \param filter Four Character Code describing the filter + * \param type Four Character Code describing the subtype - indicated whether filter is encrypt or decrypt + * \param container Pointer to the container instance + * \param status Returns the status of the operation + * \return If successful, this returns a pointer to the new instance + * of the container filter. Returns NULL on failure. + */ +VC_CONTAINER_FILTER_T *vc_container_filter_open(VC_CONTAINER_FOURCC_T filter, + VC_CONTAINER_FOURCC_T type, + VC_CONTAINER_T *container, + VC_CONTAINER_STATUS_T *status ); + +/** Closes an instance of a container filter. + * \param context Pointer to the VC_CONTAINER_FILTER_T context of the instance to close + * \return VC_CONTAINER_SUCCESS on success. + */ +VC_CONTAINER_STATUS_T vc_container_filter_close( VC_CONTAINER_FILTER_T *context ); + +/** Filter a data packet using a container filter module. + * \param context Pointer to the VC_CONTAINER_FILTER_T instance to use + * \param packet Pointer to the VC_CONTAINER_PACKET_T structure to process + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_filter_process(VC_CONTAINER_FILTER_T *context, VC_CONTAINER_PACKET_T *p_packet); + +/** Extensible control function for container filter modules. +* This function takes a variable number of arguments which will depend on the specific operation. +* +* \param context Pointer to the VC_CONTAINER_FILTER_T instance to use +* \param operation The requested operation +* \param args Arguments for the operation +* \return the status of the operation +*/ +VC_CONTAINER_STATUS_T vc_container_filter_control(VC_CONTAINER_FILTER_T *context, VC_CONTAINER_CONTROL_T operation, ... ); + +/* @} */ + +#endif /* VC_CONTAINERS_FILTERS_H */ diff --git a/containers/core/containers_index.c b/containers/core/containers_index.c new file mode 100644 index 0000000..7edebfa --- /dev/null +++ b/containers/core/containers_index.c @@ -0,0 +1,192 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_index.h" + +typedef struct { + int64_t file_offset; + int64_t time; +} VC_CONTAINER_INDEX_POS_T; + +struct VC_CONTAINER_INDEX_T { + int len; // log2 of length of entry array + int next; // next array entry to write into + int gap; // log2 of the passes through entry array to build the full list + int mgap; // len-gap, stored for convenience + int count; // number of calls to index_add since last entry added + int max_count; // log2 of the number of calls to discard between each entry added + int64_t max_time; // time of the latest entry + VC_CONTAINER_INDEX_POS_T entry[0]; // array of position/time pairs +}; + +// We have a fixed length list, and when it is full we want to discard half the entries. +// This is done without coping data by mapping the entry number to the index to the array, +// in the following way: +// Length is a power of two, so the entry number is a fixed constant bit width. The highest gap +// bits are used as a direct offset into the array (o), the lowest mgap bits are right shifted by +// gap to increment this (S). Each time we double the number of passes through the actual array. +// So if len=3, we start off with mgap=3, gap=0, we have a single pass with the trivial mapping: +// |S|S|S| [0 1 2 3 4 5 6 7] +// when this is full we change to mgap=2, gap=1, so we iterate this way: +// |o|S|S| [0 2 4 6] [1 3 5 7] +// when this is full we change to mgap=1, gap=2 +// |o|o|S| [0 4] [1 5] [2 6] [3 7] +// when this is full we change to this, which is equivalent to where we started +// |o|o|o| [0] [1] [2] [3] [4] [5] [6] [7] + +#define ENTRY(x, i) ((x)->gap == 0 ? (i) : ((i)>>(x)->mgap) + (((i) & ((1<<(x)->mgap)-1)) << (x)->gap)) + +VC_CONTAINER_STATUS_T vc_container_index_create( VC_CONTAINER_INDEX_T **index, int length ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + VC_CONTAINER_INDEX_T *id = NULL; + int len = 0; + + if(length < 16) length = 16; + if(length > 4096) length = 4096; + while((length >>= 1) != 0) + len++; + + id = malloc(sizeof(VC_CONTAINER_INDEX_T) + (sizeof(VC_CONTAINER_INDEX_POS_T)<len = id->mgap = len; + + *index = id; + return VC_CONTAINER_SUCCESS; + + error: + return status; +} + +VC_CONTAINER_STATUS_T vc_container_index_free( VC_CONTAINER_INDEX_T *index ) +{ + if(index == NULL) + return VC_CONTAINER_ERROR_FAILED; + + free(index); + return VC_CONTAINER_SUCCESS; +} + +VC_CONTAINER_STATUS_T vc_container_index_add( VC_CONTAINER_INDEX_T *index, int64_t time, int64_t file_offset ) +{ + if(index == NULL) + return VC_CONTAINER_ERROR_FAILED; + + // reject entries if they are in part of the time covered + if(index->next != 0 && time <= index->max_time) + return VC_CONTAINER_SUCCESS; + + index->count++; + if(index->count == (1<max_count)) + { + int entry; + if(index->next == (1<len)) + { + // New entry doesn't fit, we discard every other index record + // by changing how we map index positions to array entry indexes. + index->next >>= 1; + index->gap++; + index->mgap--; + index->max_count++; + + if(index->gap == index->len) + { + index->gap = 0; + index->mgap = index->len; + } + } + + entry = ENTRY(index, index->next); + + index->entry[entry].file_offset = file_offset; + index->entry[entry].time = time; + index->count = 0; + index->next++; + index->max_time = time; + } + + return VC_CONTAINER_SUCCESS; +} + +VC_CONTAINER_STATUS_T vc_container_index_get( VC_CONTAINER_INDEX_T *index, int later, int64_t *time, int64_t *file_offset, int *past ) +{ + int guess, start, end, entry; + + if(index == NULL || index->next == 0) + return VC_CONTAINER_ERROR_FAILED; + + guess = start = 0; + end = index->next-1; + + *past = *time > index->max_time; + + while(end-start > 1) + { + int64_t gtime; + guess = (start+end)>>1; + gtime = index->entry[ENTRY(index, guess)].time; + + if(*time < gtime) + end = guess; + else if(*time > gtime) + start = guess; + else + break; + } + + if (*time != index->entry[ENTRY(index, guess)].time) + { + if(later) + { + if(*time <= index->entry[ENTRY(index, start)].time) + guess = start; + else + guess = end; + } + else + { + if(*time >= index->entry[ENTRY(index, end)].time) + guess = end; + else + guess = start; + } + } + + entry = ENTRY(index, guess); + *time = index->entry[entry].time; + *file_offset = index->entry[entry].file_offset; + + return VC_CONTAINER_SUCCESS; +} + diff --git a/containers/core/containers_index.h b/containers/core/containers_index.h new file mode 100644 index 0000000..f0c49c3 --- /dev/null +++ b/containers/core/containers_index.h @@ -0,0 +1,82 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_INDEX_H +#define VC_CONTAINERS_INDEX_H + +/** \file containers_index.h + * Definition of index utilitie for containers. Creates and maintains an + * index of file offsets and times, and is able to suggest a file position + * to seek to achieve a given time target. Useful for container formats + * that don't include an index. + */ + +#include "containers/containers.h" + +struct VC_CONTAINER_INDEX_T; +typedef struct VC_CONTAINER_INDEX_T VC_CONTAINER_INDEX_T; + +/** + * Creates an index with a suggested number of entries. + * @param index Pointer to created index will be filled here on success. + * @param length Suggested length of index. + * @return Status code + */ +VC_CONTAINER_STATUS_T vc_container_index_create( VC_CONTAINER_INDEX_T **index, int length ); + + +/** + * Frees an index. + * @param index Pointer to valid index. + * @return Status code. + */ +VC_CONTAINER_STATUS_T vc_container_index_free( VC_CONTAINER_INDEX_T *index ); + + +/** + * Adds an entry to the index. If the index is full then some stored records will be + * discarded. + * @param index Pointer to a valid index. + * @param time Timestamp of new index entry. + * @param file_offset File offset for new index entry. + * @return Status code + */ +VC_CONTAINER_STATUS_T vc_container_index_add( VC_CONTAINER_INDEX_T *index, int64_t time, int64_t file_offset ); + + +/** + * Retrieves the best entry for the supplied time offset. + * @param index Pointer to valid index. + * @param later If true, the selected entry is the earliest retained entry with a greater or equal timestamp. + * If false, the selected entry is the latest retained entry with an earlier or equal timestamp. + * @param time The requested time. On success, this is filled in with the time of the selected entry. + * @param file_offset On success, this is filled in with the file offset of the selected entry. + * @param past Set if the requested time is after the last entry in the index. + * @return Status code. + */ +VC_CONTAINER_STATUS_T vc_container_index_get( VC_CONTAINER_INDEX_T *index, int later, int64_t *time, int64_t *file_offset, int *past ); + +#endif /* VC_CONTAINERS_WRITER_UTILS_H */ diff --git a/containers/core/containers_io.c b/containers/core/containers_io.c new file mode 100644 index 0000000..907a2a0 --- /dev/null +++ b/containers/core/containers_io.c @@ -0,0 +1,1088 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_uri.h" + +#define MAX_NUM_CACHED_AREAS 16 +#define MAX_NUM_MEMORY_AREAS 4 +#define NUM_TMP_MEMORY_AREAS 2 +#define MEM_CACHE_READ_MAX_SIZE (32*1024) /* Needs to be a power of 2 */ +#define MEM_CACHE_WRITE_MAX_SIZE (128*1024) /* Needs to be a power of 2 */ +#define MEM_CACHE_TMP_MAX_SIZE (32*1024) /* Needs to be a power of 2 */ +#define MEM_CACHE_ALIGNMENT (1*1024) /* Needs to be a power of 2 */ +#define MEM_CACHE_AREA_READ_MAX_SIZE (4*1024*1024) /* Needs to be a power of 2 */ + +typedef struct VC_CONTAINER_IO_PRIVATE_CACHE_T +{ + int64_t start; /**< Offset to the start of the cached area in the stream */ + int64_t end; /**< Offset to the end of the cached area in the stream */ + + int64_t offset; /**< Offset of the currently cached data in the stream */ + size_t size; /**< Size of the cached area */ + bool dirty; /**< Whether the cache is dirty and needs to be written back */ + + size_t position; /**< Current position in the cache */ + + uint8_t *buffer; /**< Pointer to the start of the valid cache area */ + uint8_t *buffer_end; /**< Pointer to the end of the cache */ + + unsigned int mem_max_size; /**< Maximum size of the memory cache */ + unsigned int mem_size; /**< Size of the memory cache */ + uint8_t *mem; /**< Pointer to the memory cache */ + + VC_CONTAINER_IO_T *io; + +} VC_CONTAINER_IO_PRIVATE_CACHE_T; + +typedef struct VC_CONTAINER_IO_PRIVATE_T +{ + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache; /**< Current cache */ + + unsigned int caches_num; + VC_CONTAINER_IO_PRIVATE_CACHE_T caches; + + unsigned int cached_areas_num; + VC_CONTAINER_IO_PRIVATE_CACHE_T cached_areas[MAX_NUM_CACHED_AREAS]; + + int64_t actual_offset; + + struct VC_CONTAINER_IO_ASYNC_T *async_io; + +} VC_CONTAINER_IO_PRIVATE_T; + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_file_open( VC_CONTAINER_IO_T *p_ctx, const char *uri, + VC_CONTAINER_IO_MODE_T mode ); +VC_CONTAINER_STATUS_T vc_container_io_null_open( VC_CONTAINER_IO_T *p_ctx, const char *uri, + VC_CONTAINER_IO_MODE_T mode ); +VC_CONTAINER_STATUS_T vc_container_io_net_open( VC_CONTAINER_IO_T *p_ctx, const char *uri, + VC_CONTAINER_IO_MODE_T mode ); +VC_CONTAINER_STATUS_T vc_container_io_pktfile_open( VC_CONTAINER_IO_T *p_ctx, const char *uri, + VC_CONTAINER_IO_MODE_T mode ); +VC_CONTAINER_STATUS_T vc_container_io_http_open( VC_CONTAINER_IO_T *p_ctx, const char *uri, + VC_CONTAINER_IO_MODE_T mode ); +static VC_CONTAINER_STATUS_T io_seek_not_seekable(VC_CONTAINER_IO_T *p_ctx, int64_t offset); + +static size_t vc_container_io_cache_read( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, uint8_t *data, size_t size ); +static int32_t vc_container_io_cache_write( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, const uint8_t *data, size_t size ); +static VC_CONTAINER_STATUS_T vc_container_io_cache_seek( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int64_t offset ); +static size_t vc_container_io_cache_refill( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache ); +static size_t vc_container_io_cache_flush( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete ); + +static struct VC_CONTAINER_IO_ASYNC_T *async_io_start( VC_CONTAINER_IO_T *io, int num_areas, VC_CONTAINER_STATUS_T * ); +static VC_CONTAINER_STATUS_T async_io_stop( struct VC_CONTAINER_IO_ASYNC_T *ctx ); +static int async_io_write( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_IO_PRIVATE_CACHE_T *cache ); +static VC_CONTAINER_STATUS_T async_io_wait_complete( struct VC_CONTAINER_IO_ASYNC_T *ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete ); +static void async_io_stats_initialise( struct VC_CONTAINER_IO_ASYNC_T *ctx, int enable ); +static void async_io_stats_get( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_WRITE_STATS_T *stats ); + +/*****************************************************************************/ +static VC_CONTAINER_IO_T *vc_container_io_open_core( const char *uri, VC_CONTAINER_IO_MODE_T mode, + VC_CONTAINER_IO_CAPABILITIES_T capabilities, + bool b_open, VC_CONTAINER_STATUS_T *p_status ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_IO_T *p_ctx = 0; + VC_CONTAINER_IO_PRIVATE_T *private = 0; + unsigned int uri_length, caches = 0, cache_max_size, num_areas = MAX_NUM_MEMORY_AREAS; + + /* XXX */ + uri_length = strlen(uri) + 1; + + /* Allocate our context before trying out the different io modules */ + p_ctx = malloc( sizeof(*p_ctx) + sizeof(*private) + uri_length); + if(!p_ctx) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(p_ctx, 0, sizeof(*p_ctx) + sizeof(*private) + uri_length ); + p_ctx->priv = private = (VC_CONTAINER_IO_PRIVATE_T *)&p_ctx[1]; + p_ctx->uri = (char *)&private[1]; + memcpy((char *)p_ctx->uri, uri, uri_length); + p_ctx->uri_parts = vc_uri_create(); + if(!p_ctx->uri_parts) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + vc_uri_parse(p_ctx->uri_parts, uri); + + if (b_open) + { + /* Open the actual i/o module */ + status = vc_container_io_null_open(p_ctx, uri, mode); + if(status) status = vc_container_io_net_open(p_ctx, uri, mode); + if(status) status = vc_container_io_pktfile_open(p_ctx, uri, mode); +#ifdef ENABLE_CONTAINER_IO_HTTP + if(status) status = vc_container_io_http_open(p_ctx, uri, mode); +#endif + if(status) status = vc_container_io_file_open(p_ctx, uri, mode); + if(status != VC_CONTAINER_SUCCESS) goto error; + + if(!p_ctx->pf_seek || (p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK)) + { + p_ctx->capabilities |= VC_CONTAINER_IO_CAPS_CANT_SEEK; + p_ctx->pf_seek = io_seek_not_seekable; + } + } + else + { + /* We're only creating an empty container i/o */ + p_ctx->capabilities = capabilities; + } + + if(p_ctx->capabilities & VC_CONTAINER_IO_CAPS_NO_CACHING) + caches = 1; + + if(mode == VC_CONTAINER_IO_MODE_WRITE) cache_max_size = MEM_CACHE_WRITE_MAX_SIZE; + else cache_max_size = MEM_CACHE_READ_MAX_SIZE; + + if(mode == VC_CONTAINER_IO_MODE_WRITE && + vc_uri_path_extension(p_ctx->uri_parts) && + !strcasecmp(vc_uri_path_extension(p_ctx->uri_parts), "tmp")) + { + caches = 1; + cache_max_size = MEM_CACHE_TMP_MAX_SIZE; + num_areas = NUM_TMP_MEMORY_AREAS; + } + + /* Check if the I/O needs caching */ + if(caches) + { + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache = &p_ctx->priv->caches; + cache->mem_max_size = cache_max_size; + cache->mem_size = cache->mem_max_size; + cache->io = p_ctx; + cache->mem = malloc(p_ctx->priv->caches.mem_size); + if(cache->mem) + { + cache->buffer = cache->mem; + cache->buffer_end = cache->mem + cache->mem_size; + p_ctx->priv->caches_num = 1; + } + } + + if(p_ctx->priv->caches_num) + p_ctx->priv->cache = &p_ctx->priv->caches; + + + /* Try to start an asynchronous io if we're in write mode and we've got at least 2 cache memory areas */ + if(mode == VC_CONTAINER_IO_MODE_WRITE && p_ctx->priv->cache && num_areas >= 2) + p_ctx->priv->async_io = async_io_start( p_ctx, num_areas, 0 ); + + end: + if(p_status) *p_status = status; + return p_ctx; + + error: + if(p_ctx) vc_uri_release(p_ctx->uri_parts); + if(p_ctx) free(p_ctx); + p_ctx = 0; + goto end; +} + +/*****************************************************************************/ +VC_CONTAINER_IO_T *vc_container_io_open( const char *uri, VC_CONTAINER_IO_MODE_T mode, + VC_CONTAINER_STATUS_T *p_status ) +{ + return vc_container_io_open_core( uri, mode, 0, true, p_status ); +} + +/*****************************************************************************/ +VC_CONTAINER_IO_T *vc_container_io_create( const char *uri, VC_CONTAINER_IO_MODE_T mode, + VC_CONTAINER_IO_CAPABILITIES_T capabilities, + VC_CONTAINER_STATUS_T *p_status ) +{ + return vc_container_io_open_core( uri, mode, capabilities, false, p_status ); +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_close( VC_CONTAINER_IO_T *p_ctx ) +{ + unsigned int i; + + if(p_ctx) + { + if(p_ctx->priv) + { + if(p_ctx->priv->caches_num) + { + if(p_ctx->priv->caches.dirty) + vc_container_io_cache_flush( p_ctx, &p_ctx->priv->caches, 1 ); + } + + if(p_ctx->priv->async_io) + async_io_stop( p_ctx->priv->async_io ); + else if(p_ctx->priv->caches_num) + free(p_ctx->priv->caches.mem); + + for(i = 0; i < p_ctx->priv->cached_areas_num; i++) + free(p_ctx->priv->cached_areas[i].mem); + + if(p_ctx->pf_close) + p_ctx->pf_close(p_ctx); + } + vc_uri_release(p_ctx->uri_parts); + free(p_ctx); + } + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +size_t vc_container_io_peek(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + size_t ret; + + if(p_ctx->priv->cache) + { + /* FIXME: do something a bit more clever than this */ + int64_t offset = p_ctx->offset; + ret = vc_container_io_read(p_ctx, buffer, size); + vc_container_io_seek(p_ctx, offset); + return ret; + } + + if (p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK) + return 0; + + ret = p_ctx->pf_read(p_ctx, buffer, size); + p_ctx->pf_seek(p_ctx, p_ctx->offset); + return ret; +} + +/*****************************************************************************/ +size_t vc_container_io_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + size_t ret; + + if(p_ctx->priv->cache) + ret = vc_container_io_cache_read( p_ctx, p_ctx->priv->cache, (uint8_t*)buffer, size ); + else + { + ret = p_ctx->pf_read(p_ctx, buffer, size); + p_ctx->priv->actual_offset += ret; + } + + p_ctx->offset += ret; + return ret; +} + +/*****************************************************************************/ +size_t vc_container_io_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size) +{ + int32_t ret; + + if(p_ctx->priv->cache) + ret = vc_container_io_cache_write( p_ctx, p_ctx->priv->cache, (const uint8_t*)buffer, size ); + else + { + ret = p_ctx->pf_write(p_ctx, buffer, size); + p_ctx->priv->actual_offset += ret; + } + + p_ctx->offset += ret; + return ret < 0 ? 0 : ret; +} + +/*****************************************************************************/ +size_t vc_container_io_skip(VC_CONTAINER_IO_T *p_ctx, size_t size) +{ + if(!size) return 0; + + if(size < 8) + { + uint8_t value[8]; + return vc_container_io_read(p_ctx, value, size); + } + + if(p_ctx->priv->cache) + { + if(vc_container_io_cache_seek(p_ctx, p_ctx->priv->cache, p_ctx->offset + size)) return 0; + p_ctx->offset += size; + return size; + } + + if(vc_container_io_seek(p_ctx, p_ctx->offset + size)) return 0; + return size; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_seek(VC_CONTAINER_IO_T *p_ctx, int64_t offset) +{ + VC_CONTAINER_STATUS_T status; + unsigned int i; + + /* Check if the requested position is in one of the cached areas */ + for(i = 0; i < p_ctx->priv->cached_areas_num; i++) + { + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache = &p_ctx->priv->cached_areas[i]; + if(offset >= cache->start && offset < cache->end) + { + p_ctx->priv->cache = cache; + break; + } + } + if(i == p_ctx->priv->cached_areas_num) + p_ctx->priv->cache = p_ctx->priv->caches_num ? &p_ctx->priv->caches : 0; + + if(p_ctx->priv->cache) + { + status = vc_container_io_cache_seek( p_ctx, p_ctx->priv->cache, offset ); + if(status == VC_CONTAINER_SUCCESS) p_ctx->offset = offset; + return status; + } + + if(p_ctx->status == VC_CONTAINER_SUCCESS && + offset == p_ctx->offset) return VC_CONTAINER_SUCCESS; + + status = p_ctx->pf_seek(p_ctx, offset); + if(status == VC_CONTAINER_SUCCESS) p_ctx->offset = offset; + p_ctx->priv->actual_offset = p_ctx->offset; + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_seek_not_seekable(VC_CONTAINER_IO_T *p_ctx, int64_t offset) +{ + VC_CONTAINER_IO_PRIVATE_T *private = p_ctx->priv; + + vc_container_assert(offset >= private->actual_offset); + if(offset == private->actual_offset) return VC_CONTAINER_SUCCESS; + + if(offset < private->actual_offset) + { + p_ctx->status = VC_CONTAINER_ERROR_EOS; + return p_ctx->status; + } + + offset -= private->actual_offset; + while(offset && !p_ctx->status) + { + uint8_t value[64]; + unsigned int ret, size = MIN(offset, 64); + ret = p_ctx->pf_read(p_ctx, value, size); + if(ret != size) p_ctx->status = VC_CONTAINER_ERROR_EOS; + offset -= ret; + } + return p_ctx->status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_control_list(VC_CONTAINER_IO_T *context, VC_CONTAINER_CONTROL_T operation, va_list args) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + if (context->pf_control) + status = context->pf_control(context, operation, args); + + /* Option to add generic I/O control here */ + + if(operation == VC_CONTAINER_CONTROL_IO_FLUSH && context->priv->cache) + { + status = VC_CONTAINER_SUCCESS; + (void)vc_container_io_cache_flush( context, context->priv->cache, 1 ); + } + + if(operation == VC_CONTAINER_CONTROL_SET_IO_PERF_STATS && context->priv->async_io) + { + status = VC_CONTAINER_SUCCESS; + async_io_stats_initialise(context->priv->async_io, va_arg(args, int)); + } + + if(operation == VC_CONTAINER_CONTROL_GET_IO_PERF_STATS && context->priv->async_io) + { + status = VC_CONTAINER_SUCCESS; + async_io_stats_get(context->priv->async_io, va_arg(args, VC_CONTAINER_WRITE_STATS_T *)); + } + + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_control(VC_CONTAINER_IO_T *context, VC_CONTAINER_CONTROL_T operation, ...) +{ + VC_CONTAINER_STATUS_T result; + va_list args; + + va_start(args, operation); + result = vc_container_io_control_list(context, operation, args); + va_end(args); + + return result; +} + +/*****************************************************************************/ +size_t vc_container_io_cache(VC_CONTAINER_IO_T *p_ctx, size_t size) +{ + VC_CONTAINER_IO_PRIVATE_T *private = p_ctx->priv; + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, *main_cache; + VC_CONTAINER_STATUS_T status; + + /* Sanity checking */ + if(private->cached_areas_num >= MAX_NUM_CACHED_AREAS) return 0; + + cache = &private->cached_areas[private->cached_areas_num]; + cache->start = p_ctx->offset; + cache->end = cache->start + size; + cache->offset = p_ctx->offset; + cache->position = 0; + cache->size = 0; + cache->io = p_ctx; + + /* Set the size of the cache area depending on the capabilities of the i/o */ + if(p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK) + cache->mem_max_size = MEM_CACHE_AREA_READ_MAX_SIZE; + else if((p_ctx->capabilities & VC_CONTAINER_IO_CAPS_SEEK_SLOW) && + size <= MEM_CACHE_AREA_READ_MAX_SIZE) + cache->mem_max_size = MEM_CACHE_AREA_READ_MAX_SIZE; + else + cache->mem_max_size = MEM_CACHE_READ_MAX_SIZE; + + cache->mem_size = size; + if(cache->mem_size > cache->mem_max_size) cache->mem_size = cache->mem_max_size; + cache->mem = malloc(cache->mem_size); + + cache->buffer = cache->mem; + cache->buffer_end = cache->mem + cache->mem_size; + + if(!cache->mem) return 0; + private->cached_areas_num++; + + /* Copy any data we've got in the current cache into the new cache */ + main_cache = p_ctx->priv->cache; + if(main_cache && main_cache->position < main_cache->size) + { + cache->size = main_cache->size - main_cache->position; + if(cache->size > cache->mem_size) cache->size = cache->mem_size; + memcpy(cache->buffer, main_cache->buffer + main_cache->position, cache->size); + main_cache->position += cache->size; + } + + /* Read the rest of the cache directly from the stream */ + if(cache->mem_size > cache->size) + { + size_t ret = cache->io->pf_read(cache->io, cache->buffer + cache->size, + cache->mem_size - cache->size); + cache->size += ret; + cache->io->priv->actual_offset = cache->offset + cache->size; + } + + status = vc_container_io_seek(p_ctx, cache->end); + if(status != VC_CONTAINER_SUCCESS) + return 0; + + if(p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK) + return cache->size; + else + return size; +} + +/*****************************************************************************/ +static size_t vc_container_io_cache_refill( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache ) +{ + size_t ret = vc_container_io_cache_flush( p_ctx, cache, 1 ); + + if(ret) return 0; /* TODO what should we do there ? */ + + if(p_ctx->priv->actual_offset != cache->offset) + { + if(cache->io->pf_seek(cache->io, cache->offset) != VC_CONTAINER_SUCCESS) + return 0; + } + + ret = cache->io->pf_read(cache->io, cache->buffer, cache->buffer_end - cache->buffer); + cache->size = ret; + cache->position = 0; + cache->io->priv->actual_offset = cache->offset + ret; + return ret; +} + +/*****************************************************************************/ +static size_t vc_container_io_cache_refill_bypass( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, uint8_t *buffer, size_t size ) +{ + size_t ret = vc_container_io_cache_flush( p_ctx, cache, 1 ); + + if(ret) return 0; /* TODO what should we do there ? */ + + if(p_ctx->priv->actual_offset != cache->offset) + { + if(cache->io->pf_seek(cache->io, cache->offset) != VC_CONTAINER_SUCCESS) + return 0; + } + + ret = cache->io->pf_read(cache->io, buffer, size); + cache->size = cache->position = 0; + cache->offset += ret; + cache->io->priv->actual_offset = cache->offset; + return ret; +} + +/*****************************************************************************/ +static size_t vc_container_io_cache_read( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, uint8_t *data, size_t size ) +{ + size_t read = 0, bytes, ret; + + while(size) + { + bytes = cache->size - cache->position; /* Bytes left in cache */ + +#if 1 // FIXME Only if stream is seekable + /* Try to read directly from the stream if the cache just gets in the way */ + if(!bytes && size > cache->mem_size) + { + bytes = cache->mem_size; + ret = vc_container_io_cache_refill_bypass( p_ctx, cache, data + read, bytes); + read += ret; + + if(ret != bytes) /* We didn't read as many bytes as we had hoped */ + goto end; + + size -= bytes; + continue; + } +#endif + + /* Refill the cache if it is empty */ + if(!bytes) bytes = vc_container_io_cache_refill( p_ctx, cache ); + if(!bytes) goto end; + + /* We do have some data in the cache so override the status */ + p_ctx->status = VC_CONTAINER_SUCCESS; + + /* Read data directly from the cache */ + if(bytes > size) bytes = size; + memcpy(data + read, cache->buffer + cache->position, bytes); + cache->position += bytes; + read += bytes; + size -= bytes; + } + + end: + vc_container_assert(cache->offset + cache->position == p_ctx->offset + read); + return read; +} + +/*****************************************************************************/ +static int32_t vc_container_io_cache_write( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, const uint8_t *data, size_t size ) +{ + int32_t written = 0; + size_t bytes, ret; + + /* If we do not have a write cache then we need to flush it */ + if(cache->size && !cache->dirty) + { + ret = vc_container_io_cache_flush( p_ctx, cache, 1 ); + if(ret) return -(int32_t)ret; + } + + while(size) + { + bytes = (cache->buffer_end - cache->buffer) - cache->position; /* Space left in cache */ + + /* Flush the cache if it is full */ + if(!bytes) + { + /* Cache full, flush it */ + ret = vc_container_io_cache_flush( p_ctx, cache, 0 ); + if(ret) + { + written -= ret; + return written; + } + continue; + } + + if(bytes > size) bytes = size; + + if(!p_ctx->priv->async_io && bytes == cache->mem_size) + { + /* Write directly from the buffer */ + ret = cache->io->pf_write(cache->io, data + written, bytes); + cache->offset += ret; + cache->io->priv->actual_offset += ret; + } + else + { + /* Write in the cache */ + memcpy(cache->buffer + cache->position, data + written, bytes); + cache->position += bytes; + cache->dirty = 1; + ret = bytes; + } + + written += ret; + if(ret != bytes) goto end; + + size -= bytes; + } + + end: + vc_container_assert(cache->offset + (int64_t)cache->position == p_ctx->offset + written); + if(cache->position > cache->size) cache->size = cache->position; + return written; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T vc_container_io_cache_seek(VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int64_t offset) +{ + VC_CONTAINER_STATUS_T status; + size_t shift, ret; + + /* Check if the seek position is within our cache */ + if(offset >= cache->offset && offset < cache->offset + (int64_t)cache->size) + { + cache->position = offset - cache->offset; + return VC_CONTAINER_SUCCESS; + } + + shift = cache->buffer - cache->mem; + if(!cache->dirty && shift && cache->size && + offset >= cache->offset - (int64_t)shift && offset < cache->offset) + { + /* We need to refill the partial bit of the cache that we didn't take care of last time */ + status = cache->io->pf_seek(cache->io, cache->offset - shift); + if(status != VC_CONTAINER_SUCCESS) return status; + cache->offset -= shift; + cache->buffer -= shift; + + ret = cache->io->pf_read(cache->io, cache->buffer, shift); + vc_container_assert(ret == shift); /* FIXME: ret must = shift */ + cache->size += shift; + cache->position = offset - cache->offset; + cache->io->priv->actual_offset = cache->offset + ret; + return VC_CONTAINER_SUCCESS; + } + + if(cache->dirty) vc_container_io_cache_flush( p_ctx, cache, 1 ); + // FIXME: what if all the data couldn't be flushed ? + + if(p_ctx->priv->async_io) async_io_wait_complete( p_ctx->priv->async_io, cache, 1 ); + + status = cache->io->pf_seek(cache->io, offset); + if(status != VC_CONTAINER_SUCCESS) return status; + + vc_container_io_cache_flush( p_ctx, cache, 1 ); + + cache->offset = offset; + cache->io->priv->actual_offset = offset; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t vc_container_io_cache_flush( VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete ) +{ + size_t ret = 0, shift; + + if(cache->position > cache->size) cache->size = cache->position; + + if(cache->dirty && cache->size) + { + if(p_ctx->priv->actual_offset != cache->offset) + { + if(p_ctx->priv->async_io) async_io_wait_complete( p_ctx->priv->async_io, cache, complete ); + + if(cache->io->pf_seek(cache->io, cache->offset) != VC_CONTAINER_SUCCESS) + return 0; + } + + if(p_ctx->priv->async_io) + { + ret = async_io_write( p_ctx->priv->async_io, cache ); + if(async_io_wait_complete( p_ctx->priv->async_io, cache, complete ) != VC_CONTAINER_SUCCESS) + ret = 0; + } + else + ret = cache->io->pf_write(cache->io, cache->buffer, cache->size); + + cache->io->priv->actual_offset = cache->offset + ret; + ret = cache->position - ret; + } + cache->dirty = 0; + + cache->offset += cache->size; + if(cache->mem_size == cache->mem_max_size) + { + shift = cache->offset &(MEM_CACHE_ALIGNMENT-1); + cache->buffer = cache->mem + shift; + } + + cache->position = cache->size = 0; + return ret; +} + +/***************************************************************************** + * Asynchronous I/O. + * This is here to keep the I/O as busy as possible by allowing the writer + * to continue its work while the I/O is taking place in the background. + *****************************************************************************/ + +#ifdef ENABLE_CONTAINERS_ASYNC_IO +#include "vcos.h" + +#define NUMPC(c,n,s) ((c) < (1<<(s)) ? (n) : ((n) / (c >> (s)))) + +static void stats_initialise(VC_CONTAINER_STATS_T *st, uint32_t shift) +{ + memset(st, 0, sizeof(VC_CONTAINER_STATS_T)); + st->shift = shift; +} + +static void stats_add_value(VC_CONTAINER_STATS_T *st, uint32_t count, uint32_t num) +{ + uint32_t numpc; + int i, j; + + if(count == 0) + return; + + numpc = NUMPC(count, num, st->shift); + // insert in the right place + i=0; + while(i < VC_CONTAINER_STATS_BINS && st->record[i].count != 0 && st->record[i].numpc > numpc) + i++; + + if(st->record[i].count != 0 && st->record[i].numpc == numpc) + { + // equal numpc, can merge now + st->record[i].count += count; + st->record[i].num += num; + } + else + { + // shift higher records up + for(j=VC_CONTAINER_STATS_BINS; j>i; j--) + st->record[j] = st->record[j-1]; + + // write record in + st->record[i].count = count; + st->record[i].num = num; + st->record[i].numpc = numpc; + + // if full, join the two closest records + if(st->record[VC_CONTAINER_STATS_BINS].count) + { + uint32_t min_diff = 0; + j = -1; + + // find closest, based on difference between numpc + for(i=0; irecord[i].numpc - st->record[i+1].numpc; + if(j == -1 || diff < min_diff) + { + j = i; + min_diff = diff; + } + } + + // merge these records + st->record[j].count += st->record[j+1].count; + st->record[j].num += st->record[j+1].num; + st->record[j].numpc = NUMPC(st->record[j].count, st->record[j].num, st->shift); + + // shift down higher records + while(++j < VC_CONTAINER_STATS_BINS) + st->record[j] = st->record[j+1]; + + // zero the free top record + st->record[VC_CONTAINER_STATS_BINS].count = 0; + st->record[VC_CONTAINER_STATS_BINS].num = 0; + st->record[VC_CONTAINER_STATS_BINS].numpc = 0; + } + } +} + +typedef struct VC_CONTAINER_IO_ASYNC_T +{ + VC_CONTAINER_IO_T *io; + VCOS_THREAD_T thread; + VCOS_SEMAPHORE_T spare_sema; + VCOS_SEMAPHORE_T queue_sema; + VCOS_EVENT_T wake_event; + int quit; + + unsigned int num_area; + uint8_t *mem[MAX_NUM_MEMORY_AREAS]; /**< Base address of memory areas */ + uint8_t *buffer[MAX_NUM_MEMORY_AREAS]; /**< When queued for writing, pointer to start of valid cache area */ + size_t size[MAX_NUM_MEMORY_AREAS]; /**< When queued for writing, size of valid area to write */ + unsigned int cur_area; + + unsigned char stack[3000]; + int error; + + int stats_enable; + VC_CONTAINER_WRITE_STATS_T stats; + +} VC_CONTAINER_IO_ASYNC_T; + +/*****************************************************************************/ +static void async_io_stats_initialise( struct VC_CONTAINER_IO_ASYNC_T *ctx, int enable ) +{ + ctx->stats_enable = enable; + stats_initialise(&ctx->stats.write, 8); + stats_initialise(&ctx->stats.wait, 0); + stats_initialise(&ctx->stats.flush, 0); +} + +static void async_io_stats_get( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_WRITE_STATS_T *stats ) +{ + *stats = ctx->stats; +} + +static void *async_io_thread(VOID *argv) +{ + VC_CONTAINER_IO_ASYNC_T *ctx = argv; + unsigned int write_area = 0; + + while (1) + { + unsigned long time = 0; + + vcos_event_wait(&ctx->wake_event); + if(ctx->quit) break; + + while(vcos_semaphore_trywait(&ctx->queue_sema) == VCOS_SUCCESS) + { + uint8_t *buffer = ctx->buffer[write_area]; + size_t size = ctx->size[write_area]; + + if(ctx->stats_enable) + time = vcos_getmicrosecs(); + + if(ctx->io->pf_write(ctx->io, buffer, size) != size) + ctx->error = 1; + + if(ctx->stats_enable) + stats_add_value(&ctx->stats.write, size, vcos_getmicrosecs() - time); + + /* Signal that the write is done */ + vcos_semaphore_post(&ctx->spare_sema); + + if(++write_area == ctx->num_area) + write_area = 0; + } + } + + return NULL; +} + +static int async_io_write( VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_IO_PRIVATE_CACHE_T *cache ) +{ + unsigned long time = 0; + unsigned int offset; + + if(ctx->stats_enable) + time = vcos_getmicrosecs(); + + /* post the current area */ + ctx->buffer[ctx->cur_area] = cache->buffer; + ctx->size[ctx->cur_area] = cache->size; + vcos_semaphore_post(&ctx->queue_sema); + vcos_event_signal(&ctx->wake_event); + + /* now we need to grab another area */ + vcos_semaphore_wait(&ctx->spare_sema); + if(++ctx->cur_area == ctx->num_area) + ctx->cur_area = 0; + + if(ctx->stats_enable) + stats_add_value(&ctx->stats.wait, 1, vcos_getmicrosecs() - time); + + /* alter cache mem to point to the new cur_area */ + offset = cache->buffer - cache->mem; + cache->mem = ctx->mem[ctx->cur_area]; + cache->buffer = cache->mem + offset; + cache->buffer_end = cache->mem + cache->mem_size; + + return ctx->error ? 0 : cache->size; +} + +static VC_CONTAINER_STATUS_T async_io_wait_complete( struct VC_CONTAINER_IO_ASYNC_T *ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete ) +{ + unsigned int time = 0; + + if(ctx->stats_enable) + time = vcos_getmicrosecs(); + + if(complete) + { + int num; + /* Need to make sure that all memory areas have been written out, so should have num-1 spare */ + for(num=0; numnum_area-1; num++) + vcos_semaphore_wait(&ctx->spare_sema); + + for(num=0; numnum_area-1; num++) + vcos_semaphore_post(&ctx->spare_sema); + } + else + { + /* Need to make sure we can acquire one memory area */ + vcos_semaphore_wait(&ctx->spare_sema); + vcos_semaphore_post(&ctx->spare_sema); + } + + if(ctx->stats_enable) + stats_add_value(&ctx->stats.flush, 1, vcos_getmicrosecs() - time); + + return ctx->error ? VC_CONTAINER_ERROR_FAILED : VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_IO_ASYNC_T *async_io_start( VC_CONTAINER_IO_T *io, int num_areas, VC_CONTAINER_STATUS_T *status ) +{ + VC_CONTAINER_IO_ASYNC_T *ctx = 0; + VCOS_UNSIGNED pri = 0; + + /* Allocate our context */ + ctx = malloc(sizeof(*ctx)); + if(!ctx) goto error_spare_sema; + memset(ctx, 0, sizeof(*ctx)); + ctx->io = io; + + ctx->mem[0] = io->priv->cache->mem; + + for(ctx->num_area = 1; ctx->num_area < num_areas; ctx->num_area++) + { + ctx->mem[ctx->num_area] = malloc(io->priv->cache->mem_size); + if(!ctx->mem[ctx->num_area]) + break; + } + + if(ctx->num_area == 1) // no real benefit in asynchronous writes + goto error_spare_sema; + + async_io_stats_initialise(ctx, 0); + + if(vcos_semaphore_create(&ctx->spare_sema, "async_spare_sem", ctx->num_area-1) != VCOS_SUCCESS) + goto error_spare_sema; + + if(vcos_semaphore_create(&ctx->queue_sema, "async_queue_sem", 0) != VCOS_SUCCESS) + goto error_queue_sema; + + if (vcos_event_create(&ctx->wake_event, "async_wake_event") != VCOS_SUCCESS) + goto error_event; + + // run this thread at a slightly higher priority than the calling thread - that means that + // we prefer to write to the SD card rather than filling the memory buffer. + pri = vcos_thread_get_priority(vcos_thread_current()); + if(vcos_thread_create_classic(&ctx->thread, "async_io", async_io_thread, ctx, + ctx->stack, sizeof(ctx->stack), pri-1, 10, VCOS_START) != VCOS_SUCCESS) + goto error_thread; + + if(status) *status = VC_CONTAINER_SUCCESS; + return ctx; + + error_thread: + vcos_event_delete(&ctx->wake_event); + error_event: + vcos_semaphore_delete(&ctx->queue_sema); + error_queue_sema: + vcos_semaphore_delete(&ctx->spare_sema); + error_spare_sema: + if(ctx) free(ctx); + if(status) *status = VC_CONTAINER_ERROR_FAILED; + return 0; +} + +static VC_CONTAINER_STATUS_T async_io_stop( VC_CONTAINER_IO_ASYNC_T *ctx ) +{ + /* Block if a write operation is already in progress */ + //vcos_semaphore_wait(&ctx->sema); + // XXX block until all done + + ctx->quit = 1; + vcos_event_signal(&ctx->wake_event); + vcos_thread_join(&ctx->thread,NULL); + vcos_event_delete(&ctx->wake_event); + vcos_semaphore_delete(&ctx->queue_sema); + vcos_semaphore_delete(&ctx->spare_sema); + + while(ctx->num_area > 0) + free(ctx->mem[--ctx->num_area]); + + free(ctx); + return VC_CONTAINER_SUCCESS; +} +#else + +static struct VC_CONTAINER_IO_ASYNC_T *async_io_start( VC_CONTAINER_IO_T *io, int num_areas, VC_CONTAINER_STATUS_T *status ) +{ + VC_CONTAINER_PARAM_UNUSED(io); + VC_CONTAINER_PARAM_UNUSED(num_areas); + if(status) *status = VC_CONTAINER_ERROR_FAILED; + return 0; +} + +static int async_io_write( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_IO_PRIVATE_CACHE_T *cache ) +{ + VC_CONTAINER_PARAM_UNUSED(ctx); + VC_CONTAINER_PARAM_UNUSED(cache); + return 0; +} + +static VC_CONTAINER_STATUS_T async_io_wait_complete( struct VC_CONTAINER_IO_ASYNC_T *ctx, + VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete ) +{ + VC_CONTAINER_PARAM_UNUSED(ctx); + VC_CONTAINER_PARAM_UNUSED(cache); + VC_CONTAINER_PARAM_UNUSED(complete); + return 0; +} + +static VC_CONTAINER_STATUS_T async_io_stop( struct VC_CONTAINER_IO_ASYNC_T *ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(ctx); + return VC_CONTAINER_SUCCESS; +} + +static void async_io_stats_initialise( struct VC_CONTAINER_IO_ASYNC_T *ctx, int enable ) +{ + VC_CONTAINER_PARAM_UNUSED(ctx); + VC_CONTAINER_PARAM_UNUSED(enable); +} + +static void async_io_stats_get( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_WRITE_STATS_T *stats ) +{ + VC_CONTAINER_PARAM_UNUSED(ctx); + VC_CONTAINER_PARAM_UNUSED(stats); +} + + +#endif diff --git a/containers/core/containers_io.h b/containers/core/containers_io.h new file mode 100644 index 0000000..8cd4407 --- /dev/null +++ b/containers/core/containers_io.h @@ -0,0 +1,229 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_IO_H +#define VC_CONTAINERS_IO_H + +/** \file containers_io.h + * Interface definition for the input / output abstraction layer used by container + * readers and writers */ +#include "containers/containers.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup VcContainerIoApi Container I/O API */ +/* @{ */ + +/** Container io opening mode. + * This is used to specify whether a reader or writer is requested. + */ +typedef enum { + VC_CONTAINER_IO_MODE_READ = 0, /**< Container io opened in reading mode */ + VC_CONTAINER_IO_MODE_WRITE = 1 /**< Container io opened in writing mode */ +} VC_CONTAINER_IO_MODE_T; + +/** \name Container I/O Capabilities + * The following flags are exported by container i/o modules to describe their capabilities */ +/* @{ */ +/** Type definition for container I/O capabilities */ +typedef uint32_t VC_CONTAINER_IO_CAPABILITIES_T; +/** Seeking is not supported */ +#define VC_CONTAINER_IO_CAPS_CANT_SEEK 0x1 +/** Seeking is slow and should be avoided */ +#define VC_CONTAINER_IO_CAPS_SEEK_SLOW 0x2 +/** The I/O doesn't provide any caching of the data */ +#define VC_CONTAINER_IO_CAPS_NO_CACHING 0x4 +/* @} */ + +/** Container Input / Output Context. + * This structure defines the context for a container io instance */ +struct VC_CONTAINER_IO_T +{ + /** Pointer to information private to the container io instance */ + struct VC_CONTAINER_IO_PRIVATE_T *priv; + + /** Pointer to information private to the container io module */ + struct VC_CONTAINER_IO_MODULE_T *module; + + /** Uniform Resource Identifier for the stream to open. + * This is a string encoded in UTF-8 which follows the syntax defined in + * RFC2396 (http://tools.ietf.org/html/rfc2396). */ + char *uri; + + /** Pre-parsed URI */ + struct VC_URI_PARTS_T *uri_parts; + + /** Current offset into the i/o stream */ + int64_t offset; + + /** Current size of the i/o stream (0 if unknown). This size might grow during the + * lifetime of the i/o instance (for instance when doing progressive download). */ + int64_t size; + + /** Capabilities of the i/o stream */ + VC_CONTAINER_IO_CAPABILITIES_T capabilities; + + /** Status of the i/o stream */ + VC_CONTAINER_STATUS_T status; + + /** Maximum size allowed for this i/o stream (0 if unknown). This is used during writing + * to limit the size of the stream to below this value. */ + int64_t max_size; + + /** \note the following list of function pointers should not be used directly. + * They defines the interface for implementing container io modules and are filled in + * by the container modules themselves. */ + + /** \private + * Function pointer to close and free all resources allocated by a + * container io module */ + VC_CONTAINER_STATUS_T (*pf_close)(struct VC_CONTAINER_IO_T *io); + + /** \private + * Function pointer to read or skip data from container io module */ + size_t (*pf_read)(struct VC_CONTAINER_IO_T *io, void *buffer, size_t size); + + /** \private + * Function pointer to write data to a container io module */ + size_t (*pf_write)(struct VC_CONTAINER_IO_T *io, const void *buffer, size_t size); + + /** \private + * Function pointer to seek into a container io module */ + VC_CONTAINER_STATUS_T (*pf_seek)(struct VC_CONTAINER_IO_T *io, int64_t offset); + + /** \private + * Function pointer to perform a control operation on a container io module */ + VC_CONTAINER_STATUS_T (*pf_control)(struct VC_CONTAINER_IO_T *io, + VC_CONTAINER_CONTROL_T operation, va_list args); + +}; + +/** Opens an i/o stream pointed to by a URI. + * This will create an instance of the container i/o module. + * + * \param uri Uniform Resource Identifier pointing to the multimedia container + * \param mode Mode in which the i/o stream will be opened + * \param status Returns the status of the operation + * \return If successful, this returns a pointer to the new instance + * of the i/o module. Returns NULL on failure. + */ +VC_CONTAINER_IO_T *vc_container_io_open( const char *uri, VC_CONTAINER_IO_MODE_T mode, + VC_CONTAINER_STATUS_T *status ); + +/** Creates an empty i/o stream. The i/o function pointers will have to be set + * by the caller before the i/o gets used. + * This will create an instance of the container i/o module. + * + * \param uri Uniform Resource Identifier pointing to the multimedia container + * \param mode Mode in which the i/o stream will be opened + * \param capabilities Flags indicating the capabilities of the i/o + * \param status Returns the status of the operation + * \return If successful, this returns a pointer to the new instance + * of the i/o module. Returns NULL on failure. + */ +VC_CONTAINER_IO_T *vc_container_io_create( const char *uri, VC_CONTAINER_IO_MODE_T mode, + VC_CONTAINER_IO_CAPABILITIES_T capabilities, + VC_CONTAINER_STATUS_T *p_status ); + +/** Closes an instance of a container i/o module. + * \param context Pointer to the VC_CONTAINER_IO_T context of the instance to close + * \return VC_CONTAINER_SUCCESS on success. + */ +VC_CONTAINER_STATUS_T vc_container_io_close( VC_CONTAINER_IO_T *context ); + +/** Read data from an i/o stream without advancing the read position within the stream. + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param buffer Pointer to the buffer where the data will be read + * \param size Number of bytes to read + * \return The size of the data actually read. + */ +size_t vc_container_io_peek(VC_CONTAINER_IO_T *context, void *buffer, size_t size); + +/** Read data from an i/o stream. + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param buffer Pointer to the buffer where the data will be read + * \param size Number of bytes to read + * \return The size of the data actually read. + */ +size_t vc_container_io_read(VC_CONTAINER_IO_T *context, void *buffer, size_t size); + +/** Skip data in an i/o stream without reading it. + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param size Number of bytes to skip + * \return The size of the data actually skipped. + */ +size_t vc_container_io_skip(VC_CONTAINER_IO_T *context, size_t size); + +/** Write data to an i/o stream. + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param buffer Pointer to the buffer containing the data to write + * \param size Number of bytes to write + * \return The size of the data actually written. + */ +size_t vc_container_io_write(VC_CONTAINER_IO_T *context, const void *buffer, size_t size); + +/** Seek into an i/o stream. + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param offset Absolute file offset to seek to + * \return Status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_io_seek(VC_CONTAINER_IO_T *context, int64_t offset); + +/** Perform control operation on an i/o stream (va_list). + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param operation Control operation to be performed + * \param args Additional arguments for the operation + * \return Status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_io_control_list(VC_CONTAINER_IO_T *context, + VC_CONTAINER_CONTROL_T operation, va_list args); + +/** Perform control operation on an i/o stream (varargs). + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param operation Control operation to be performed + * \param ... Additional arguments for the operation + * \return Status of the operation + */ +VC_CONTAINER_STATUS_T vc_container_io_control(VC_CONTAINER_IO_T *context, + VC_CONTAINER_CONTROL_T operation, ...); + +/** Cache the pointed region of the i/o stream (from current position). + * This will allow future seeking into the specified region even on non-seekable streams. + * \param context Pointer to the VC_CONTAINER_IO_T instance to use + * \param size Size of the region to cache + * \return Status of the operation + */ +size_t vc_container_io_cache(VC_CONTAINER_IO_T *context, size_t size); + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* VC_CONTAINERS_HELPERS_H */ diff --git a/containers/core/containers_io_helpers.c b/containers/core/containers_io_helpers.c new file mode 100644 index 0000000..88e08cc --- /dev/null +++ b/containers/core/containers_io_helpers.c @@ -0,0 +1,244 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_logging.h" + +void vc_container_helper_format_debug(VC_CONTAINER_T *ctx, int indent, const char *format, ...) +{ + char debug_string[512]; + va_list args; + int result; + + if(indent >= (int)sizeof(debug_string)) return; + memset(debug_string, ' ', indent); + + va_start( args, format ); + result = vsnprintf(debug_string + indent, sizeof(debug_string) - indent, format, args); + va_end( args ); + + if(result <= 0) return; + + vc_container_log(ctx, VC_CONTAINER_LOG_FORMAT, debug_string); + fflush(0); +} + +uint64_t vc_container_helper_int_debug(VC_CONTAINER_T *ctx, int type, uint64_t value, const char *name, int indent) +{ + VC_CONTAINER_PARAM_UNUSED(ctx); + + if(type == LOG_FORMAT_TYPE_HEX) + vc_container_helper_format_debug(ctx, indent, "%s: 0x%"PRIx64, name, value); + else + vc_container_helper_format_debug(ctx, indent, "%s: %"PRIi64, name, value); + return value; +} + +uint64_t vc_container_helper_read_debug(VC_CONTAINER_T *ctx, int type, int size, + const char *name, uint8_t *buffer, int indent, int b_skip) +{ + int64_t offset = STREAM_POSITION(ctx); + uint64_t value = 0; + GUID_T guid; + + if(type == LOG_FORMAT_TYPE_STRING || + type == LOG_FORMAT_TYPE_STRING_UTF16_LE || + type == LOG_FORMAT_TYPE_STRING_UTF16_BE) + { + uint8_t stringbuf[256]; + char utf8buf[256]; + int stringsize = sizeof(stringbuf) - 2; + + if(!buffer) + { + buffer = stringbuf; + if(size < stringsize) stringsize = size; + } + else stringsize = size; + + value = vc_container_io_read(ctx->priv->io, buffer, stringsize); + + if(!utf8_from_charset(type == LOG_FORMAT_TYPE_STRING ? "UTF8" : "UTF16-LE", + utf8buf, sizeof(utf8buf), buffer, stringsize)) + vc_container_helper_format_debug(ctx, indent, "%s: \"%s\"", name, utf8buf); + else + vc_container_helper_format_debug(ctx, indent, "%s: (could not read)", name); + + if(size - stringsize) + value += vc_container_io_skip(ctx->priv->io, size - stringsize); + return value; + } + + if(type == LOG_FORMAT_TYPE_UINT_LE) + { + switch(size) + { + case 1: value = vc_container_io_read_uint8(ctx->priv->io); break; + case 2: value = vc_container_io_read_le_uint16(ctx->priv->io); break; + case 3: value = vc_container_io_read_le_uint24(ctx->priv->io); break; + case 4: value = vc_container_io_read_le_uint32(ctx->priv->io); break; + case 5: value = vc_container_io_read_le_uint40(ctx->priv->io); break; + case 6: value = vc_container_io_read_le_uint48(ctx->priv->io); break; + case 7: value = vc_container_io_read_le_uint56(ctx->priv->io); break; + case 8: value = vc_container_io_read_le_uint64(ctx->priv->io); break; + } + } + else if(type == LOG_FORMAT_TYPE_UINT_BE) + { + switch(size) + { + case 1: value = vc_container_io_read_uint8(ctx->priv->io); break; + case 2: value = vc_container_io_read_be_uint16(ctx->priv->io); break; + case 3: value = vc_container_io_read_be_uint24(ctx->priv->io); break; + case 4: value = vc_container_io_read_be_uint32(ctx->priv->io); break; + case 5: value = vc_container_io_read_be_uint40(ctx->priv->io); break; + case 6: value = vc_container_io_read_be_uint48(ctx->priv->io); break; + case 7: value = vc_container_io_read_be_uint56(ctx->priv->io); break; + case 8: value = vc_container_io_read_be_uint64(ctx->priv->io); break; + } + } + else if(type == LOG_FORMAT_TYPE_FOURCC) + { + value = vc_container_io_read_fourcc(ctx->priv->io); + } + else if(type == LOG_FORMAT_TYPE_GUID) + { + value = vc_container_io_read(ctx->priv->io, &guid, 16); + } + else + { + vc_container_assert(0); + return 0; + } + + if(type == LOG_FORMAT_TYPE_GUID) + { + if(value == 16) + { + vc_container_helper_format_debug(ctx, indent, "%s: 0x%x-0x%x-0x%x-0x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x", + name, guid.word0, guid.short0, guid.short1, + guid.bytes[0], guid.bytes[1], guid.bytes[2], guid.bytes[3], + guid.bytes[4], guid.bytes[5], guid.bytes[6], guid.bytes[7]); + if(buffer) memcpy(buffer, &guid, sizeof(guid)); + } + } + else if(type == LOG_FORMAT_TYPE_FOURCC) + { + uint32_t val = value; + vc_container_helper_format_debug(ctx, indent, "%s: %4.4s", name, (char *)&val); + } + else + { + vc_container_helper_format_debug(ctx, indent, "%s: %"PRIi64, name, value); + } + + if(b_skip) value = (STREAM_POSITION(ctx) - offset) != size; + return value; +} + +VC_CONTAINER_STATUS_T vc_container_helper_write_debug(VC_CONTAINER_T *ctx, int type, int size, + const char *name, uint64_t value, const uint8_t *buffer, int indent, int silent) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + if(type == LOG_FORMAT_TYPE_STRING) + { + value = vc_container_io_write(ctx->priv->io, buffer, size); + if(!silent) + vc_container_helper_format_debug(ctx, indent, "%s: \"%ls\"", name, buffer); + return value == (uint64_t)size ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; + } + + if(type == LOG_FORMAT_TYPE_UINT_LE) + { + switch(size) + { + case 1: status = vc_container_io_write_uint8(ctx->priv->io, (uint8_t)value); break; + case 2: status = vc_container_io_write_le_uint16(ctx->priv->io, (uint16_t)value); break; + case 3: status = vc_container_io_write_le_uint24(ctx->priv->io, (uint32_t)value); break; + case 4: status = vc_container_io_write_le_uint32(ctx->priv->io, (uint32_t)value); break; + case 8: status = vc_container_io_write_le_uint64(ctx->priv->io, value); break; + } + } + else if(type == LOG_FORMAT_TYPE_UINT_BE) + { + switch(size) + { + case 1: status = vc_container_io_write_uint8(ctx->priv->io, (uint8_t)value); break; + case 2: status = vc_container_io_write_be_uint16(ctx->priv->io, (uint16_t)value); break; + case 3: status = vc_container_io_write_be_uint24(ctx->priv->io, (uint32_t)value); break; + case 4: status = vc_container_io_write_be_uint32(ctx->priv->io, (uint32_t)value); break; + case 8: status = vc_container_io_write_be_uint64(ctx->priv->io, value); break; + } + } + else if(type == LOG_FORMAT_TYPE_FOURCC) + { + status = vc_container_io_write_fourcc(ctx->priv->io, (uint32_t)value); + } + else if(type == LOG_FORMAT_TYPE_GUID) + { + value = vc_container_io_write(ctx->priv->io, buffer, 16); + } + else + { + vc_container_assert(0); + return 0; + } + + if(status) + { + vc_container_helper_format_debug(ctx, indent, "write failed for %s", name); + return status; + } + + if(!silent) + { + if (type == LOG_FORMAT_TYPE_FOURCC) + { + vc_container_helper_format_debug(ctx, indent, "%s: %4.4s", name, (char *)&value); + } + else if(type == LOG_FORMAT_TYPE_GUID) + { + GUID_T guid; + memcpy(&guid, buffer, sizeof(guid)); + vc_container_helper_format_debug(ctx, indent, "%s: 0x%x-0x%x-0x%x-0x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x", + name, guid.word0, guid.short0, guid.short1, + guid.bytes[0], guid.bytes[1], guid.bytes[2], guid.bytes[3], + guid.bytes[4], guid.bytes[5], guid.bytes[6], guid.bytes[7]); + } + else + { + vc_container_helper_format_debug(ctx, indent, "%s: %"PRIi64, name, value); + } + } + + return status; +} diff --git a/containers/core/containers_io_helpers.h b/containers/core/containers_io_helpers.h new file mode 100644 index 0000000..1f9b460 --- /dev/null +++ b/containers/core/containers_io_helpers.h @@ -0,0 +1,716 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_IO_HELPERS_H +#define VC_CONTAINERS_IO_HELPERS_H + +/** \file containers_io_helpers.h + * Helper functions and macros which provide functionality which is often used by containers + */ + +#include "containers/core/containers_io.h" +#include "containers/core/containers_utils.h" + +/***************************************************************************** + * Helper inline functions to read integers from an i/o stream + *****************************************************************************/ + +/** Reads an unsigned 8 bits integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint8_t vc_container_io_read_uint8(VC_CONTAINER_IO_T *io) +{ + uint8_t value; + size_t ret = vc_container_io_read(io, &value, 1); + return ret == 1 ? value : 0; +} + +/** Reads a FOURCC from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The FOURCC to read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE VC_CONTAINER_FOURCC_T vc_container_io_read_fourcc(VC_CONTAINER_IO_T *io) +{ + VC_CONTAINER_FOURCC_T value; + size_t ret = vc_container_io_read(io, (int8_t *)&value, 4); + return ret == 4 ? value : 0; +} + +/** Reads an unsigned 16 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint16_t vc_container_io_read_be_uint16(VC_CONTAINER_IO_T *io) +{ + uint8_t value[2]; + size_t ret = vc_container_io_read(io, value, 2); + return ret == 2 ? (value[0] << 8) | value[1] : 0; +} + +/** Reads an unsigned 24 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_read_be_uint24(VC_CONTAINER_IO_T *io) +{ + uint8_t value[3]; + size_t ret = vc_container_io_read(io, value, 3); + return ret == 3 ? (value[0] << 16) | (value[1] << 8) | value[2] : 0; +} + +/** Reads an unsigned 32 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_read_be_uint32(VC_CONTAINER_IO_T *io) +{ + uint8_t value[4]; + size_t ret = vc_container_io_read(io, value, 4); + return ret == 4 ? (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3] : 0; +} + +/** Reads an unsigned 40 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_be_uint40(VC_CONTAINER_IO_T *io) +{ + uint8_t value[5]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 5); + + value1 = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; + value2 = value[4]; + + return ret == 5 ? (((uint64_t)value1) << 8)|value2 : 0; +} + +/** Reads an unsigned 48 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_be_uint48(VC_CONTAINER_IO_T *io) +{ + uint8_t value[6]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 6); + + value1 = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; + value2 = (value[4] << 8) | value[5]; + + return ret == 6 ? (((uint64_t)value1) << 16)|value2 : 0; +} + +/** Reads an unsigned 56 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_be_uint56(VC_CONTAINER_IO_T *io) +{ + uint8_t value[7]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 7); + + value1 = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; + value2 = (value[4] << 16) | (value[5] << 8) | value[6]; + + return ret == 7 ? (((uint64_t)value1) << 24)|value2 : 0; +} + +/** Reads an unsigned 64 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_be_uint64(VC_CONTAINER_IO_T *io) +{ + uint8_t value[8]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 8); + + value1 = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; + value2 = (value[4] << 24) | (value[5] << 16) | (value[6] << 8) | value[7]; + + return ret == 8 ? (((uint64_t)value1) << 32)|value2 : 0; +} + +/** Reads an unsigned 16 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint16_t vc_container_io_read_le_uint16(VC_CONTAINER_IO_T *io) +{ + uint8_t value[2]; + size_t ret = vc_container_io_read(io, value, 2); + return ret == 2 ? (value[1] << 8) | value[0] : 0; +} + +/** Reads an unsigned 24 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_read_le_uint24(VC_CONTAINER_IO_T *io) +{ + uint8_t value[3]; + size_t ret = vc_container_io_read(io, value, 3); + return ret == 3 ? (value[2] << 16) | (value[1] << 8) | value[0] : 0; +} + +/** Reads an unsigned 32 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_read_le_uint32(VC_CONTAINER_IO_T *io) +{ + uint8_t value[4]; + size_t ret = vc_container_io_read(io, value, 4); + return ret == 4 ? (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0] : 0; +} + +/** Reads an unsigned 40 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_le_uint40(VC_CONTAINER_IO_T *io) +{ + uint8_t value[5]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 5); + + value1 = (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + value2 = value[4]; + + return ret == 5 ? (((uint64_t)value2) << 32)|value1 : 0; +} + +/** Reads an unsigned 48 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_le_uint48(VC_CONTAINER_IO_T *io) +{ + uint8_t value[6]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 6); + + value1 = (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + value2 = (value[5] << 8) | value[4]; + + return ret == 6 ? (((uint64_t)value2) << 32)|value1 : 0; +} + +/** Reads an unsigned 56 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_le_uint56(VC_CONTAINER_IO_T *io) +{ + uint8_t value[7]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 7); + + value1 = (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + value2 = (value[6] << 16) | (value[5] << 8) | value[4]; + + return ret == 7 ? (((uint64_t)value2) << 32)|value1 : 0; +} + +/** Reads an unsigned 64 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_read_le_uint64(VC_CONTAINER_IO_T *io) +{ + uint8_t value[8]; + uint32_t value1, value2; + size_t ret = vc_container_io_read(io, value, 8); + + value1 = (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + value2 = (value[7] << 24) | (value[6] << 16) | (value[5] << 8) | value[4]; + + return ret == 8 ? (((uint64_t)value2) << 32)|value1 : 0; +} + +/***************************************************************************** + * Helper inline functions to peek integers from an i/o stream + *****************************************************************************/ + +/** Peeks an unsigned 8 bits integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint8_t vc_container_io_peek_uint8(VC_CONTAINER_IO_T *io) +{ + uint8_t value; + size_t ret = vc_container_io_peek(io, &value, 1); + return ret == 1 ? value : 0; +} + +/** Peeks an unsigned 16 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint16_t vc_container_io_peek_be_uint16(VC_CONTAINER_IO_T *io) +{ + uint8_t value[2]; + size_t ret = vc_container_io_peek(io, value, 2); + return ret == 2 ? (value[0] << 8) | value[1] : 0; +} + +/** Peeks an unsigned 24 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_peek_be_uint24(VC_CONTAINER_IO_T *io) +{ + uint8_t value[3]; + size_t ret = vc_container_io_peek(io, value, 3); + return ret == 3 ? (value[0] << 16) | (value[1] << 8) | value[2] : 0; +} + +/** Peeks an unsigned 32 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_peek_be_uint32(VC_CONTAINER_IO_T *io) +{ + uint8_t value[4]; + size_t ret = vc_container_io_peek(io, value, 4); + return ret == 4 ? (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3] : 0; +} + +/** Peeks an unsigned 64 bits big endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_peek_be_uint64(VC_CONTAINER_IO_T *io) +{ + uint8_t value[8]; + uint32_t value1, value2; + size_t ret = vc_container_io_peek(io, value, 8); + + value1 = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; + value2 = (value[4] << 24) | (value[5] << 16) | (value[6] << 8) | value[7]; + + return ret == 8 ? (((uint64_t)value1) << 32)|value2 : 0; +} + +/** Peeks an unsigned 16 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint16_t vc_container_io_peek_le_uint16(VC_CONTAINER_IO_T *io) +{ + uint8_t value[2]; + size_t ret = vc_container_io_peek(io, value, 2); + return ret == 2 ? (value[1] << 8) | value[0] : 0; +} + +/** Peeks an unsigned 24 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_peek_le_uint24(VC_CONTAINER_IO_T *io) +{ + uint8_t value[3]; + size_t ret = vc_container_io_peek(io, value, 3); + return ret == 3 ? (value[2] << 16) | (value[1] << 8) | value[0] : 0; +} + +/** Peeks an unsigned 32 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint32_t vc_container_io_peek_le_uint32(VC_CONTAINER_IO_T *io) +{ + uint8_t value[4]; + size_t ret = vc_container_io_peek(io, value, 4); + return ret == 4 ? (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0] : 0; +} + +/** Peeks an unsigned 64 bits little endian integer from an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \return The integer read. In case of failure during the read, + * this will return a value of 0. + */ +STATIC_INLINE uint64_t vc_container_io_peek_le_uint64(VC_CONTAINER_IO_T *io) +{ + uint8_t value[8]; + uint32_t value1, value2; + size_t ret = vc_container_io_peek(io, value, 8); + + value1 = (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + value2 = (value[7] << 24) | (value[6] << 16) | (value[5] << 8) | value[4]; + + return ret == 8 ? (((uint64_t)value2) << 32)|value1 : 0; +} + +/***************************************************************************** + * Helper inline functions to write integers to an i/o stream + *****************************************************************************/ + +/** Writes an unsigned 8 bits integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_uint8(VC_CONTAINER_IO_T *io, uint8_t value) +{ + size_t ret = vc_container_io_write(io, &value, 1); + return ret == 1 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes a FOURCC to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The FOURCC to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_fourcc(VC_CONTAINER_IO_T *io, VC_CONTAINER_FOURCC_T value) +{ + size_t ret = vc_container_io_write(io, (uint8_t *)&value, 4); + return ret == 4 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 16 bits big endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_be_uint16(VC_CONTAINER_IO_T *io, uint16_t value) +{ + uint8_t bytes[2] = {(uint8_t)(value >> 8), (uint8_t)value}; + size_t ret = vc_container_io_write(io, bytes, 2); + return ret == 2 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 24 bits big endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_be_uint24(VC_CONTAINER_IO_T *io, uint32_t value) +{ + uint8_t bytes[3] = {(uint8_t)(value >> 16), (uint8_t)(value >> 8), (uint8_t)value}; + size_t ret = vc_container_io_write(io, bytes, 3); + return ret == 3 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 32 bits big endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_be_uint32(VC_CONTAINER_IO_T *io, uint32_t value) +{ + uint8_t bytes[4] = {value >> 24, value >> 16, value >> 8, value}; + size_t ret = vc_container_io_write(io, bytes, 4); + return ret == 4 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 64 bits big endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_be_uint64(VC_CONTAINER_IO_T *io, uint64_t value) +{ + uint8_t bytes[8] = + { + (uint8_t)(value >> 56), + (uint8_t)(value >> 48), + (uint8_t)(value >> 40), + (uint8_t)(value >> 32), + (uint8_t)(value >> 24), + (uint8_t)(value >> 16), + (uint8_t)(value >> 8), + (uint8_t) value + }; + size_t ret = vc_container_io_write(io, bytes, 8); + return ret == 8 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 16 bits little endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_le_uint16(VC_CONTAINER_IO_T *io, uint16_t value) +{ + uint8_t bytes[2] = {(uint8_t)value, (uint8_t)(value >> 8)}; + size_t ret = vc_container_io_write(io, bytes, 2); + return ret == 2 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 24 bits little endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_le_uint24(VC_CONTAINER_IO_T *io, uint32_t value) +{ + uint8_t bytes[3] = {value, value >> 8, value >> 16}; + size_t ret = vc_container_io_write(io, bytes, 3); + return ret == 3 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 32 bits little endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_le_uint32(VC_CONTAINER_IO_T *io, uint32_t value) +{ + uint8_t bytes[4] = {value, value >> 8, value >> 16, value >> 24}; + size_t ret = vc_container_io_write(io, bytes, 4); + return ret == 4 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/** Writes an unsigned 64 bits little endian integer to an i/o stream. + * \param io Pointer to the VC_CONTAINER_IO_T instance to use + * \param value The integer to write. + * \return The status of the operation. + */ +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_io_write_le_uint64(VC_CONTAINER_IO_T *io, uint64_t value) +{ + uint8_t bytes[8] = + { + (uint8_t) value, + (uint8_t)(value >> 8), + (uint8_t)(value >> 16), + (uint8_t)(value >> 24), + (uint8_t)(value >> 32), + (uint8_t)(value >> 40), + (uint8_t)(value >> 48), + (uint8_t)(value >> 56) + }; + size_t ret = vc_container_io_write(io, bytes, 8); + return ret == 8 ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FAILED; +} + +/***************************************************************************** + * Helper macros for accessing the i/o stream. These will also call the right + * functions depending on the endianness defined. + *****************************************************************************/ + +/** Macro which returns the current position within the stream */ +#define STREAM_POSITION(ctx) (ctx)->priv->io->offset +/** Macro which returns true if the end of stream has been reached */ +#define STREAM_EOS(ctx) ((ctx)->priv->io->status == VC_CONTAINER_ERROR_EOS) +/** Macro which returns the status of the stream */ +#define STREAM_STATUS(ctx) (ctx)->priv->io->status +/** Macro which returns true if an error other than end of stream has occurred */ +#define STREAM_ERROR(ctx) ((ctx)->priv->io->status && (ctx)->priv->io->status != VC_CONTAINER_ERROR_EOS) +/** Macro which returns true if we can seek into the stream */ +#define STREAM_SEEKABLE(ctx) (!((ctx)->priv->io->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK)) + +#define PEEK_BYTES(ctx, buffer, size) vc_container_io_peek((ctx)->priv->io, buffer, (size_t)(size)) +#define READ_BYTES(ctx, buffer, size) vc_container_io_read((ctx)->priv->io, buffer, (size_t)(size)) +#define SKIP_BYTES(ctx, size) vc_container_io_skip((ctx)->priv->io, (size_t)(size)) +#define SEEK(ctx, off) vc_container_io_seek((ctx)->priv->io, (int64_t)(off)) +#define CACHE_BYTES(ctx, size) vc_container_io_cache((ctx)->priv->io, (size_t)(size)) + +#define _SKIP_GUID(ctx) vc_container_io_skip((ctx)->priv->io, 16) +#define _SKIP_U8(ctx) (vc_container_io_skip((ctx)->priv->io, 1) != 1) +#define _SKIP_U16(ctx) (vc_container_io_skip((ctx)->priv->io, 2) != 2) +#define _SKIP_U24(ctx) (vc_container_io_skip((ctx)->priv->io, 3) != 3) +#define _SKIP_U32(ctx) (vc_container_io_skip((ctx)->priv->io, 4) != 4) +#define _SKIP_U64(ctx) (vc_container_io_skip((ctx)->priv->io, 8) != 8) +#define _SKIP_FOURCC(ctx) (vc_container_io_skip((ctx)->priv->io, 4) != 4) + +#define _READ_GUID(ctx, buffer) vc_container_io_read((ctx)->priv->io, buffer, 16) +#define _READ_U8(ctx) vc_container_io_read_uint8((ctx)->priv->io) +#define _READ_FOURCC(ctx) vc_container_io_read_fourcc((ctx)->priv->io) +#define PEEK_GUID(ctx, buffer) vc_container_io_peek((ctx)->priv->io, buffer, 16) +#define PEEK_U8(ctx) vc_container_io_peek_uint8((ctx)->priv->io) +#ifdef CONTAINER_IS_BIG_ENDIAN +# define _READ_U16(ctx) vc_container_io_read_be_uint16((ctx)->priv->io) +# define _READ_U24(ctx) vc_container_io_read_be_uint24((ctx)->priv->io) +# define _READ_U32(ctx) vc_container_io_read_be_uint32((ctx)->priv->io) +# define _READ_U40(ctx) vc_container_io_read_be_uint40((ctx)->priv->io) +# define _READ_U48(ctx) vc_container_io_read_be_uint48((ctx)->priv->io) +# define _READ_U56(ctx) vc_container_io_read_be_uint56((ctx)->priv->io) +# define _READ_U64(ctx) vc_container_io_read_be_uint64((ctx)->priv->io) +# define PEEK_U16(ctx) vc_container_io_peek_be_uint16((ctx)->priv->io) +# define PEEK_U24(ctx) vc_container_io_peek_be_uint24((ctx)->priv->io) +# define PEEK_U32(ctx) vc_container_io_peek_be_uint32((ctx)->priv->io) +# define PEEK_U64(ctx) vc_container_io_peek_be_uint64((ctx)->priv->io) +#else +# define _READ_U16(ctx) vc_container_io_read_le_uint16((ctx)->priv->io) +# define _READ_U24(ctx) vc_container_io_read_le_uint24((ctx)->priv->io) +# define _READ_U32(ctx) vc_container_io_read_le_uint32((ctx)->priv->io) +# define _READ_U40(ctx) vc_container_io_read_le_uint40((ctx)->priv->io) +# define _READ_U48(ctx) vc_container_io_read_le_uint48((ctx)->priv->io) +# define _READ_U56(ctx) vc_container_io_read_le_uint56((ctx)->priv->io) +# define _READ_U64(ctx) vc_container_io_read_le_uint64((ctx)->priv->io) +# define PEEK_U16(ctx) vc_container_io_peek_le_uint16((ctx)->priv->io) +# define PEEK_U24(ctx) vc_container_io_peek_le_uint24((ctx)->priv->io) +# define PEEK_U32(ctx) vc_container_io_peek_le_uint32((ctx)->priv->io) +# define PEEK_U64(ctx) vc_container_io_peek_le_uint64((ctx)->priv->io) +#endif + +#define WRITE_BYTES(ctx, buffer, size) vc_container_io_write((ctx)->priv->io, buffer, (size_t)(size)) +#define _WRITE_GUID(ctx, buffer) vc_container_io_write((ctx)->priv->io, buffer, 16) +#define _WRITE_U8(ctx, v) vc_container_io_write_uint8((ctx)->priv->io, v) +#define _WRITE_FOURCC(ctx, v) vc_container_io_write_fourcc((ctx)->priv->io, v) +#ifdef CONTAINER_IS_BIG_ENDIAN +# define _WRITE_U16(ctx, v) vc_container_io_write_be_uint16((ctx)->priv->io, v) +# define _WRITE_U24(ctx, v) vc_container_io_write_be_uint24((ctx)->priv->io, v) +# define _WRITE_U32(ctx, v) vc_container_io_write_be_uint32((ctx)->priv->io, v) +# define _WRITE_U64(ctx, v) vc_container_io_write_be_uint64((ctx)->priv->io, v) +#else +# define _WRITE_U16(ctx, v) vc_container_io_write_le_uint16((ctx)->priv->io, v) +# define _WRITE_U24(ctx, v) vc_container_io_write_le_uint24((ctx)->priv->io, v) +# define _WRITE_U32(ctx, v) vc_container_io_write_le_uint32((ctx)->priv->io, v) +# define _WRITE_U64(ctx, v) vc_container_io_write_le_uint64((ctx)->priv->io, v) +#endif + +#ifndef CONTAINER_HELPER_LOG_INDENT +# define CONTAINER_HELPER_LOG_INDENT(a) 0 +#endif + +#ifdef CONTAINER_IS_BIG_ENDIAN +# define LOG_FORMAT_TYPE_UINT LOG_FORMAT_TYPE_UINT_BE +# define LOG_FORMAT_TYPE_STRING_UTF16 LOG_FORMAT_TYPE_STRING_UTF16_BE +#else +# define LOG_FORMAT_TYPE_UINT LOG_FORMAT_TYPE_UINT_LE +# define LOG_FORMAT_TYPE_STRING_UTF16 LOG_FORMAT_TYPE_STRING_UTF16_LE +#endif + +#ifndef ENABLE_CONTAINERS_LOG_FORMAT +#define SKIP_GUID(ctx,n) _SKIP_GUID(ctx) +#define SKIP_U8(ctx,n) _SKIP_U8(ctx) +#define SKIP_U16(ctx,n) _SKIP_U16(ctx) +#define SKIP_U24(ctx,n) _SKIP_U24(ctx) +#define SKIP_U32(ctx,n) _SKIP_U32(ctx) +#define SKIP_U64(ctx,n) _SKIP_U64(ctx) +#define SKIP_FOURCC(ctx,n) _SKIP_FOURCC(ctx) +#define READ_GUID(ctx,buffer,n) _READ_GUID(ctx,(uint8_t *)buffer) +#define READ_U8(ctx,n) _READ_U8(ctx) +#define READ_U16(ctx,n) _READ_U16(ctx) +#define READ_U24(ctx,n) _READ_U24(ctx) +#define READ_U32(ctx,n) _READ_U32(ctx) +#define READ_U40(ctx,n) _READ_U40(ctx) +#define READ_U48(ctx,n) _READ_U48(ctx) +#define READ_U56(ctx,n) _READ_U56(ctx) +#define READ_U64(ctx,n) _READ_U64(ctx) +#define READ_FOURCC(ctx,n) _READ_FOURCC(ctx) +#define READ_STRING(ctx,buffer,sz,n) READ_BYTES(ctx,buffer,sz) +#define READ_STRING_UTF16(ctx,buffer,sz,n) READ_BYTES(ctx,buffer,sz) +#define SKIP_STRING(ctx,sz,n) SKIP_BYTES(ctx,sz) +#define SKIP_STRING_UTF16(ctx,sz,n) SKIP_BYTES(ctx,sz) +#else +#define SKIP_GUID(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_GUID, 16, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define SKIP_U8(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 1, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define SKIP_U16(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 2, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define SKIP_U24(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 3, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define SKIP_U32(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 4, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define SKIP_U64(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 8, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define SKIP_FOURCC(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_FOURCC, 4, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define READ_GUID(ctx,buffer,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_GUID, 16, n, (uint8_t *)buffer, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U8(ctx,n) (uint8_t)vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 1, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U16(ctx,n) (uint16_t)vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 2, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U24(ctx,n) (uint32_t)vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 3, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U32(ctx,n) (uint32_t)vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 4, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U40(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 5, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U48(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 6, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U56(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 7, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_U64(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_UINT, 8, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_FOURCC(ctx,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_FOURCC, 4, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_STRING_UTF16(ctx,buffer,sz,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_STRING_UTF16, sz, n, (uint8_t *)buffer, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define READ_STRING(ctx,buffer,sz,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_STRING, sz, n, (uint8_t *)buffer, CONTAINER_HELPER_LOG_INDENT(ctx), 0) +#define SKIP_STRING_UTF16(ctx,sz,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_STRING_UTF16, sz, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#define SKIP_STRING(ctx,sz,n) vc_container_helper_read_debug(ctx, LOG_FORMAT_TYPE_STRING, sz, n, 0, CONTAINER_HELPER_LOG_INDENT(ctx), 1) +#endif + +#ifndef ENABLE_CONTAINERS_LOG_FORMAT +#define WRITE_GUID(ctx,buffer,n) _WRITE_GUID(ctx,(const uint8_t *)buffer) +#define WRITE_U8(ctx,v,n) _WRITE_U8(ctx,(uint8_t)(v)) +#define WRITE_FOURCC(ctx,v,n) _WRITE_FOURCC(ctx,(uint32_t)(v)) +#define WRITE_U16(ctx,v,n) _WRITE_U16(ctx,(uint16_t)(v)) +#define WRITE_U24(ctx,v,n) _WRITE_U24(ctx,(uint32_t)(v)) +#define WRITE_U32(ctx,v,n) _WRITE_U32(ctx,(uint32_t)(v)) +#define WRITE_U64(ctx,v,n) _WRITE_U64(ctx,(uint64_t)(v)) +#define WRITE_STRING(ctx,buffer,size,n) WRITE_BYTES(ctx, buffer, size) +#else +#define WRITE_GUID(ctx,buffer,n) (vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_GUID, 16, n, UINT64_C(0), (const uint8_t *)buffer, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) ? 0 : 16) +#define WRITE_U8(ctx,v,n) vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_UINT, 1, n, (uint64_t)(v), 0, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) +#define WRITE_FOURCC(ctx,v,n) vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_FOURCC, 4, n, (uint64_t)(v), 0, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) +#define WRITE_U16(ctx,v,n) (uint16_t)vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_UINT, 2, n, (uint64_t)(v), 0, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) +#define WRITE_U24(ctx,v,n) vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_UINT, 3, n, (uint64_t)(v), 0, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) +#define WRITE_U32(ctx,v,n) vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_UINT, 4, n, (uint64_t)(v), 0, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) +#define WRITE_U64(ctx,v,n) vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_UINT, 8, n, (uint64_t)(v), 0, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) +#define WRITE_STRING(ctx,buffer,size,n) (vc_container_helper_write_debug(ctx, LOG_FORMAT_TYPE_STRING, size, n, UINT64_C(0), (const uint8_t *)buffer, CONTAINER_HELPER_LOG_INDENT(ctx), !(ctx)->priv->io->module) ? 0 : size) +#endif + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT +#define LOG_FORMAT(ctx, ...) do { if((ctx)->priv->io->module) vc_container_helper_format_debug(ctx, CONTAINER_HELPER_LOG_INDENT(ctx), __VA_ARGS__); } while(0) +#else +#define LOG_FORMAT(ctx, ...) do {} while (0) +#endif + +#define LOG_FORMAT_TYPE_UINT_LE 0 +#define LOG_FORMAT_TYPE_UINT_BE 1 +#define LOG_FORMAT_TYPE_STRING 2 +#define LOG_FORMAT_TYPE_STRING_UTF16_LE 3 +#define LOG_FORMAT_TYPE_STRING_UTF16_BE 4 +#define LOG_FORMAT_TYPE_FOURCC 5 +#define LOG_FORMAT_TYPE_GUID 6 +#define LOG_FORMAT_TYPE_HEX 0x100 + +uint64_t vc_container_helper_int_debug(VC_CONTAINER_T *ctx, int type, uint64_t value, const char *name, int indent); +uint64_t vc_container_helper_read_debug(VC_CONTAINER_T *ctx, int type, int size, const char *name, + uint8_t *buffer, int indent, int b_skip); +VC_CONTAINER_STATUS_T vc_container_helper_write_debug(VC_CONTAINER_T *ctx, int type, int size, const char *name, + uint64_t value, const uint8_t *buffer, int indent, int silent); +void vc_container_helper_format_debug(VC_CONTAINER_T *ctx, int indent, const char *format, ...); + +#endif /* VC_CONTAINERS_IO_HELPERS_H */ +/* End of file */ +/*-----------------------------------------------------------------------------*/ diff --git a/containers/core/containers_list.c b/containers/core/containers_list.c new file mode 100644 index 0000000..333f052 --- /dev/null +++ b/containers/core/containers_list.c @@ -0,0 +1,221 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include + +#include "containers/core/containers_common.h" +#include "containers/core/containers_list.h" + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +/****************************************************************************** +Type definitions +******************************************************************************/ + +/****************************************************************************** +Function prototypes +******************************************************************************/ + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/** Find an entry in the list, or the insertion point. + * Uses binary sub-division to find the search item. If index is not NULL, the + * index of the matching entry, or the point at which to insert if not found, is + * written to that address. + * + * \param list The list to be searched. + * \param entry The entry for which to search. + * \param index Set to index of match, or insertion point if not found. May be NULL. + * \return True if a match was found, false if not. */ +static bool vc_containers_list_find_index(const VC_CONTAINERS_LIST_T *list, + const void *entry, + uint32_t *index) +{ + const char *entries = (const char *)list->entries; + size_t entry_size = list->entry_size; + VC_CONTAINERS_LIST_COMPARATOR_T comparator = list->comparator; + uint32_t start = 0, end = list->size; + uint32_t mid = end >> 1; + bool match = false; + + while (mid < end) + { + int comparison = comparator(entry, entries + mid * entry_size); + + if (comparison < 0) + end = mid; + else if (comparison > 0) + start = mid + 1; + else { + match = true; + break; + } + + mid = (start + end) >> 1; + } + + if (index) *index = mid; + return match; +} + +/****************************************************************************** +Functions exported as part of the API +******************************************************************************/ + +/*****************************************************************************/ +VC_CONTAINERS_LIST_T *vc_containers_list_create(uint32_t capacity, + size_t entry_size, + VC_CONTAINERS_LIST_COMPARATOR_T comparator) +{ + VC_CONTAINERS_LIST_T *list; + + list = (VC_CONTAINERS_LIST_T *)malloc(sizeof(VC_CONTAINERS_LIST_T)); + if (!list) + return NULL; + + /* Ensure non-zero capacity, as that signifies a read-only list */ + if (!capacity) capacity = 1; + + list->entries = malloc(capacity * entry_size); + if (!list->entries) + { + free(list); + return NULL; + } + + list->size = 0; + list->capacity = capacity; + list->entry_size = entry_size; + list->comparator = comparator; + + return list; +} + +/*****************************************************************************/ +void vc_containers_list_destroy(VC_CONTAINERS_LIST_T *list) +{ + /* Avoid trying to destroy read-only lists */ + if (list && list->capacity) + { + if (list->entries) + free(list->entries); + free(list); + } +} + +/*****************************************************************************/ +void vc_containers_list_reset(VC_CONTAINERS_LIST_T *list) +{ + /* Avoid trying to reset read-only lists */ + if (list && list->capacity) + list->size = 0; +} + +/*****************************************************************************/ +bool vc_containers_list_insert(VC_CONTAINERS_LIST_T *list, + void *new_entry, + bool allow_duplicates) +{ + uint32_t insert_idx; + char *insert_ptr; + size_t entry_size; + bool match; + + if (!list || !list->capacity) return false; + + entry_size = list->entry_size; + match = vc_containers_list_find_index(list, new_entry, &insert_idx); + insert_ptr = (char *)list->entries + entry_size * insert_idx; + + if (!match || allow_duplicates) + { + /* Ensure there is space for the new entry */ + if (list->size == list->capacity) + { + void *new_entries = realloc(list->entries, (list->size + 1) * entry_size); + + if (!new_entries) + return false; + list->entries = new_entries; + list->capacity++; + } + + /* Move up anything above the insertion point */ + if (insert_idx < list->size) + memmove(insert_ptr + entry_size, insert_ptr, (list->size - insert_idx) * entry_size); + + list->size++; + } + + /* Copy in the new entry (overwriting the old one if necessary) */ + memcpy(insert_ptr, new_entry, list->entry_size); + + return true; +} + +/*****************************************************************************/ +bool vc_containers_list_find_entry(const VC_CONTAINERS_LIST_T *list, + void *entry) +{ + uint32_t index; + size_t entry_size; + + if (!vc_containers_list_find_index(list, entry, &index)) + return false; + + entry_size = list->entry_size; + memcpy(entry, (const char *)list->entries + entry_size * index, entry_size); + + return true; +} + +/*****************************************************************************/ +void vc_containers_list_validate(const VC_CONTAINERS_LIST_T *list) +{ + uint32_t ii, entry_size; + const uint8_t *entry_ptr; + + vc_container_assert(list); + vc_container_assert(!list->capacity || list->size <= list->capacity); + vc_container_assert(list->entry_size); + vc_container_assert(list->comparator); + vc_container_assert(list->entries); + + /* Check all entries are in sorted order */ + entry_ptr = (const uint8_t *)list->entries; + entry_size = list->entry_size; + for (ii = 1; ii < list->size; ii++) + { + vc_container_assert(list->comparator(entry_ptr, entry_ptr + entry_size) <= 0); + entry_ptr += entry_size; + } +} diff --git a/containers/core/containers_list.h b/containers/core/containers_list.h new file mode 100644 index 0000000..5b08997 --- /dev/null +++ b/containers/core/containers_list.h @@ -0,0 +1,102 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _VC_CONTAINERS_LIST_H_ +#define _VC_CONTAINERS_LIST_H_ + +#include "containers/containers.h" + +/** List entry comparison prototype. + * Returns zero if items at a and b match, positive if a is "bigger" than b and + * negative if a is "smaller" than b. */ +typedef int (*VC_CONTAINERS_LIST_COMPARATOR_T)(const void *a, const void *b); + +/** Sorted list type. + * Storage type providing efficient insertion and search via binary sub-division. */ +typedef struct vc_containers_list_tag +{ + uint32_t size; /**< Number of defined entries in list */ + uint32_t capacity; /**< Capacity of list, in entries, or zero for read-only */ + size_t entry_size; /**< Size of one entry, in bytes */ + VC_CONTAINERS_LIST_COMPARATOR_T comparator; /**< Entry comparison function */ + void *entries; /**< Pointer to array of entries */ +} VC_CONTAINERS_LIST_T; + +/** Macro to generate a static, read-only list from an array and comparator */ +#define VC_CONTAINERS_STATIC_LIST(L, A, C) static VC_CONTAINERS_LIST_T L = { countof(A), 0, sizeof(*(A)), (VC_CONTAINERS_LIST_COMPARATOR_T)(C), A } + + +/** Create an empty list. + * The list is created based on the details provided, minimum capacity one entry. + * + * \param The initial capacity in entries. + * \param entry_size The size of each entry, in bytes. + * \param comparator A function for comparing two entries. + * \return The new list or NULL. */ +VC_CONTAINERS_LIST_T *vc_containers_list_create(uint32_t capacity, size_t entry_size, VC_CONTAINERS_LIST_COMPARATOR_T comparator); + +/** Destroy a list. + * Has no effect on a static list. + * + * \param list The list to be destroyed. */ +void vc_containers_list_destroy(VC_CONTAINERS_LIST_T *list); + +/** Reset a list to be empty. + * Has no effect on a static list. + * + * \param list The list to be reset. */ +void vc_containers_list_reset(VC_CONTAINERS_LIST_T *list); + +/** Insert an entry into the list. + * + * \param list The list. + * \param new_entry The new entry to be inserted. + * \param allow_duplicates Determines whether to insert or overwrite if there + * is an existing matching entry. + * \return True if the entry has successfully been inserted, false if the list + * needed to be enlarged and the memory allocation failed. */ +bool vc_containers_list_insert(VC_CONTAINERS_LIST_T *list, void *new_entry, bool allow_duplicates); + +/** Find an entry in the list and fill in the result. + * Searches for an entry in the list using the comparator and if found + * overwrites the one passed in with the one found. + * + * \param list The list to search. + * \param entry An entry with enough defined to find it in the list, filled in + * with the rest if found. + * \return True if found, false if not. */ +bool vc_containers_list_find_entry(const VC_CONTAINERS_LIST_T *list, void *entry); + +/** Validates a list pointer. + * Fields and contents of a list are checked and asserted to be correct. With a + * large list this may be slow, so it is recommended only to call this in debug + * builds. + * + * \param list The list to be validated. */ +void vc_containers_list_validate(const VC_CONTAINERS_LIST_T *list); + +#endif /* _VC_CONTAINERS_LIST_H_ */ diff --git a/containers/core/containers_loader.c b/containers/core/containers_loader.c new file mode 100644 index 0000000..b597301 --- /dev/null +++ b/containers/core/containers_loader.c @@ -0,0 +1,436 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_loader.h" + +#if !defined(ENABLE_CONTAINERS_STANDALONE) + #include "vcos_dlfcn.h" + #define DL_SUFFIX VCOS_SO_EXT + #ifndef DL_PATH_PREFIX + #define DL_PATH_PREFIX "" + #endif +#endif + +/****************************************************************************** +Type definitions. +******************************************************************************/ + +typedef VC_CONTAINER_STATUS_T (*VC_CONTAINER_READER_OPEN_FUNC_T)(VC_CONTAINER_T *); +typedef VC_CONTAINER_STATUS_T (*VC_CONTAINER_WRITER_OPEN_FUNC_T)(VC_CONTAINER_T *); + +/****************************************************************************** +Prototypes for local functions +******************************************************************************/ + +static void reset_context(VC_CONTAINER_T *p_ctx); +static VC_CONTAINER_READER_OPEN_FUNC_T load_library(void **handle, const char *name, const char *ext, int read); +static void unload_library(void *handle); +static VC_CONTAINER_READER_OPEN_FUNC_T load_reader(void **handle, const char *name); +static VC_CONTAINER_READER_OPEN_FUNC_T load_writer(void **handle, const char *name); +static VC_CONTAINER_READER_OPEN_FUNC_T load_metadata_reader(void **handle, const char *name); +static const char* container_for_fileext(const char *fileext); + +/******************************************************************************** + List of supported containers + ********************************************************************************/ + +static const char *readers[] = +{"mp4", "asf", "avi", "mkv", "wav", "flv", "simple", "rawvideo", "mpga", "ps", "rtp", "rtsp", "rcv", "rv9", "qsynth", "binary", 0}; +static const char *writers[] = +{"mp4", "asf", "avi", "binary", "simple", "rawvideo", 0}; +static const char *metadata_readers[] = +{"id3", 0}; + +#if defined(ENABLE_CONTAINERS_STANDALONE) +VC_CONTAINER_STATUS_T asf_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T avi_writer_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T mp4_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T mp4_writer_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T mpga_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T mkv_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T wav_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T flv_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T ps_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T rtp_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T binary_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T binary_writer_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T rcv_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T rv9_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T qsynth_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T simple_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T simple_writer_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T rawvideo_reader_open( VC_CONTAINER_T * ); +VC_CONTAINER_STATUS_T rawvideo_writer_open( VC_CONTAINER_T * ); + +VC_CONTAINER_STATUS_T id3_metadata_reader_open( VC_CONTAINER_T * ); + +static struct +{ + const char *name; + VC_CONTAINER_READER_OPEN_FUNC_T func; +} reader_entry_points[] = +{ + {"asf", &asf_reader_open}, + {"avi", &avi_reader_open}, + {"mpga", &mpga_reader_open}, + {"mkv", &mkv_reader_open}, + {"wav", &wav_reader_open}, + {"mp4", &mp4_reader_open}, + {"flv", &flv_reader_open}, + {"ps", &ps_reader_open}, + {"binary", &binary_reader_open}, + {"rtp", &rtp_reader_open}, + {"rtsp", &rtsp_reader_open}, + {"rcv", &rcv_reader_open}, + {"rv9", &rv9_reader_open}, + {"qsynth", &qsynth_reader_open}, + {"simple", &simple_reader_open}, + {"rawvideo", &rawvideo_reader_open}, + {0, 0} +}; + +static struct +{ + const char *name; + VC_CONTAINER_READER_OPEN_FUNC_T func; +} metadata_reader_entry_points[] = +{ + {"id3", &id3_metadata_reader_open}, + {0, 0} +}; + +static struct +{ + const char *name; + VC_CONTAINER_WRITER_OPEN_FUNC_T func; +} writer_entry_points[] = +{ + {"avi", &avi_writer_open}, + {"mp4", &mp4_writer_open}, + {"binary", &binary_writer_open}, + {"simple", &simple_writer_open}, + {"rawvideo", &rawvideo_writer_open}, + {0, 0} +}; +#endif /* defined(ENABLE_CONTAINERS_STANDALONE) */ + +/** Table describing the mapping between file extensions and container name. + This is only used as optimisation to decide which container to try first. + Entries where the file extension and container have the same name can be omitted. */ +static const struct { + const char *extension; + const char *container; +} extension_container_mapping[] = +{ + { "wma", "asf" }, + { "wmv", "asf" }, + { "mov", "mp4" }, + { "3gp", "mp4" }, + { "mp2", "mpga" }, + { "mp3", "mpga" }, + { "webm", "mkv" }, + { "mid", "qsynth" }, + { "mld", "qsynth" }, + { "mmf", "qsynth" }, + { 0, 0 } +}; + +/******************************************************************************** + Public functions + ********************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_load_reader(VC_CONTAINER_T *p_ctx, const char *fileext) +{ + const char *name; + void *handle = NULL; + VC_CONTAINER_READER_OPEN_FUNC_T func; + VC_CONTAINER_STATUS_T status; + unsigned int i; + int64_t offset; + + vc_container_assert(p_ctx && !p_ctx->priv->module_handle); + + /* FIXME: the missing part here is code that reads a configuration or + searches the filesystem for container libraries. Instead, we currently + rely on static arrays i.e. 'readers', 'writers', etc. */ + + /* Before trying proper container readers, iterate through metadata + readers to parse tags concatenated to start/end of stream */ + for(i = 0; metadata_readers[i]; i++) + { + if ((func = load_metadata_reader(&handle, metadata_readers[i])) != NULL) + { + status = (*func)(p_ctx); + if(!status && p_ctx->priv->pf_close) p_ctx->priv->pf_close(p_ctx); + reset_context(p_ctx); + unload_library(handle); + if(status == VC_CONTAINER_SUCCESS) break; + if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; + } + } + + /* Store the current position, in case any containers don't leave the stream + at the start, and the IO layer can cope with the seek */ + offset = p_ctx->priv->io->offset; + + /* Now move to containers, try to find a readers using the file extension to name + mapping first */ + if (fileext && (name = container_for_fileext(fileext)) != NULL && (func = load_reader(&handle, name)) != NULL) + { + status = (*func)(p_ctx); + if(status == VC_CONTAINER_SUCCESS) goto success; + unload_library(handle); + if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; + } + + /* If there was no suitable mapping, iterate through all readers. */ + for(i = 0; readers[i]; i++) + { + if ((func = load_reader(&handle, readers[i])) != NULL) + { + if(vc_container_io_seek(p_ctx->priv->io, offset) != VC_CONTAINER_SUCCESS) + { + unload_library(handle); + goto error; + } + + status = (*func)(p_ctx); + if(status == VC_CONTAINER_SUCCESS) goto success; + reset_context(p_ctx); + unload_library(handle); + if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; + } + } + + error: + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + success: + p_ctx->priv->module_handle = handle; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_load_writer(VC_CONTAINER_T *p_ctx, const char *fileext) +{ + const char *name; + void *handle = NULL; + VC_CONTAINER_WRITER_OPEN_FUNC_T func; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FAILED; + unsigned int i; + + vc_container_assert(p_ctx && !p_ctx->priv->module_handle); + + /* Do we have a container mapping for this file extension? */ + if ((name = container_for_fileext(fileext)) != NULL && (func = load_writer(&handle, name)) != NULL) + { + status = (*func)(p_ctx); + if(status == VC_CONTAINER_SUCCESS) goto success; + unload_library(handle); + if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; + } + + /* If there was no suitable mapping, iterate through all writers. */ + for(i = 0; writers[i]; i++) + { + if ((func = load_writer(&handle, writers[i])) != NULL) + { + status = (*func)(p_ctx); + if(status == VC_CONTAINER_SUCCESS) goto success; + unload_library(handle); + if (status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) goto error; + } + } + + error: + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + success: + p_ctx->priv->module_handle = handle; + return status; +} + +/*****************************************************************************/ +void vc_container_unload(VC_CONTAINER_T *p_ctx) +{ + if (p_ctx->priv->module_handle) + { + unload_library(p_ctx->priv->module_handle); + p_ctx->priv->module_handle = NULL; + } +} + +/****************************************************************************** +Local Functions +******************************************************************************/ +static void reset_context(VC_CONTAINER_T *p_ctx) +{ + vc_container_assert(p_ctx); + + p_ctx->capabilities = 0; + p_ctx->tracks = NULL; + p_ctx->tracks_num = 0; + p_ctx->drm = NULL; + p_ctx->priv->module = NULL; + p_ctx->priv->pf_close = NULL; + p_ctx->priv->pf_read = NULL; + p_ctx->priv->pf_write = NULL; + p_ctx->priv->pf_seek = NULL; + p_ctx->priv->pf_control = NULL; + p_ctx->priv->tmp_io = NULL; +} + +/*****************************************************************************/ +static VC_CONTAINER_READER_OPEN_FUNC_T load_reader(void **handle, const char *name) +{ + return load_library(handle, name, NULL, 1); +} + +/*****************************************************************************/ +static VC_CONTAINER_READER_OPEN_FUNC_T load_writer(void **handle, const char *name) +{ + return load_library(handle, name, NULL, 0); +} + +/*****************************************************************************/ +static VC_CONTAINER_READER_OPEN_FUNC_T load_metadata_reader(void **handle, const char *name) +{ + #define DL_PREFIX_METADATA "metadata_" + return load_library(handle, name, DL_PREFIX_METADATA, 1); +} + +#if !defined(ENABLE_CONTAINERS_STANDALONE) + +/*****************************************************************************/ +static VC_CONTAINER_READER_OPEN_FUNC_T load_library(void **handle, const char *name, const char *ext, int read) +{ + #define DL_PREFIX_RD "reader_" + #define DL_PREFIX_WR "writer_" + const char *entrypt_read = {"reader_open"}; + const char *entrypt_write = {"writer_open"}; + char *dl_name, *entrypt_name; + void *dl_handle; + VC_CONTAINER_READER_OPEN_FUNC_T func = NULL; + unsigned dl_size, ep_size, name_len = strlen(name) + (ext ? strlen(ext) : 0); + + vc_container_assert(read == 0 || read == 1); + + dl_size = strlen(DL_PATH_PREFIX) + MAX(strlen(DL_PREFIX_RD), strlen(DL_PREFIX_WR)) + name_len + strlen(DL_SUFFIX) + 1; + if ((dl_name = malloc(dl_size)) == NULL) + return NULL; + + ep_size = name_len + 1 + MAX(strlen(entrypt_read), strlen(entrypt_write)) + 1; + if ((entrypt_name = malloc(ep_size)) == NULL) + { + free(dl_name); + return NULL; + } + + 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); + snprintf(entrypt_name, ep_size, "%s_%s%s", name, ext ? ext : "", read ? entrypt_read : entrypt_write); + + if ( (dl_handle = vcos_dlopen(dl_name, VCOS_DL_NOW)) != NULL ) + { + /* Try generic entrypoint name before the mangled, full name */ + func = (VC_CONTAINER_READER_OPEN_FUNC_T)vcos_dlsym(dl_handle, read ? entrypt_read : entrypt_write); +#if !defined(__VIDEOCORE__) /* The following would be pointless on MW/VideoCore */ + if (!func) func = (VC_CONTAINER_READER_OPEN_FUNC_T)vcos_dlsym(dl_handle, entrypt_name); +#endif + /* Only return handle if symbol found */ + if (func) + *handle = dl_handle; + else + vcos_dlclose(dl_handle); + } + + free(entrypt_name); + free(dl_name); + return func; +} + +/*****************************************************************************/ +static void unload_library(void *handle) +{ + vcos_dlclose(handle); +} + +#else /* !defined(ENABLE_CONTAINERS_STANDALONE) */ + +/*****************************************************************************/ +static VC_CONTAINER_READER_OPEN_FUNC_T load_library(void **handle, const char *name, const char *ext, int read) +{ + int i; + VC_CONTAINER_PARAM_UNUSED(handle); + VC_CONTAINER_PARAM_UNUSED(ext); + + if (read) + { + for (i = 0; reader_entry_points[i].name; i++) + if (!strcasecmp(reader_entry_points[i].name, name)) + return reader_entry_points[i].func; + + for (i = 0; metadata_reader_entry_points[i].name; i++) + if (!strcasecmp(metadata_reader_entry_points[i].name, name)) + return metadata_reader_entry_points[i].func; + } + else + { + for (i = 0; writer_entry_points[i].name; i++) + if (!strcasecmp(writer_entry_points[i].name, name)) + return writer_entry_points[i].func; + } + + return NULL; +} + +/*****************************************************************************/ +static void unload_library(void *handle) +{ + (void)handle; +} + +#endif /* !defined(ENABLE_CONTAINERS_STANDALONE) */ + +/*****************************************************************************/ +static const char* container_for_fileext(const char *fileext) +{ + int i; + + for( i = 0; fileext && extension_container_mapping[i].extension; i++ ) + { + if (!strcasecmp( fileext, extension_container_mapping[i].extension )) + return extension_container_mapping[i].container; + } + + return fileext; +} diff --git a/containers/core/containers_loader.h b/containers/core/containers_loader.h new file mode 100644 index 0000000..946eaf6 --- /dev/null +++ b/containers/core/containers_loader.h @@ -0,0 +1,41 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VC_CONTAINERS_LOADER_H +#define VC_CONTAINERS_LOADER_H + +/** Find and attempt to load & open reader, 'fileext' is a hint that can be used + to speed up loading. */ +VC_CONTAINER_STATUS_T vc_container_load_reader(VC_CONTAINER_T *p_ctx, const char *fileext); + +/** Find and attempt to load & open writer, 'fileext' is a hint used to help in + selecting the appropriate container format. */ +VC_CONTAINER_STATUS_T vc_container_load_writer(VC_CONTAINER_T *p_ctx, const char *fileext); + +void vc_container_unload(VC_CONTAINER_T *p_ctx); + +#endif /* VC_CONTAINERS_LOADER_H */ diff --git a/containers/core/containers_logging.c b/containers/core/containers_logging.c new file mode 100644 index 0000000..5a739a3 --- /dev/null +++ b/containers/core/containers_logging.c @@ -0,0 +1,111 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#include "containers/containers.h" +#include "containers/core/containers_private.h" +#include "containers/core/containers_logging.h" + +#ifndef ENABLE_CONTAINERS_STANDALONE +# include "vcos.h" +#endif + +#ifdef __ANDROID__ +#define LOG_TAG "ContainersCore" +#include +#endif + +/* Default verbosity that will be inherited by containers */ +static uint32_t default_verbosity_mask = VC_CONTAINER_LOG_ALL; + +/* By default log everything that's not associated with a container context */ +static uint32_t verbosity_mask = VC_CONTAINER_LOG_ALL; + +void vc_container_log_set_default_verbosity(uint32_t mask) +{ + default_verbosity_mask = mask; +} + +uint32_t vc_container_log_get_default_verbosity(void) +{ + return default_verbosity_mask; +} + +void vc_container_log_set_verbosity(VC_CONTAINER_T *ctx, uint32_t mask) +{ + if(!ctx) verbosity_mask = mask; + else ctx->priv->verbosity = mask; +} + +void vc_container_log(VC_CONTAINER_T *ctx, VC_CONTAINER_LOG_TYPE_T type, const char *format, ...) +{ + uint32_t verbosity = ctx ? ctx->priv->verbosity : verbosity_mask; + va_list args; + + // Optimise out the call to vc_container_log_vargs etc. when it won't do anything. + if(!(type & verbosity)) return; + + va_start( args, format ); + vc_container_log_vargs(ctx, type, format, args); + va_end( args ); +} + +void vc_container_log_vargs(VC_CONTAINER_T *ctx, VC_CONTAINER_LOG_TYPE_T type, const char *format, va_list args) +{ + uint32_t verbosity = ctx ? ctx->priv->verbosity : verbosity_mask; + + // If the verbosity is such that the type doesn't need logging quit now. + if(!(type & verbosity)) return; + +#ifdef __ANDROID__ + { + // Default to Android's "verbose" level (doesn't usually come out) + android_LogPriority logLevel = ANDROID_LOG_VERBOSE; + + // Where type suggest a higher level is required update logLevel. + // (Usually type contains only 1 bit as set by the LOG_DEBUG, LOG_ERROR or LOG_INFO macros) + if (type & VC_CONTAINER_LOG_ERROR) + logLevel = ANDROID_LOG_ERROR; + else if (type & VC_CONTAINER_LOG_INFO) + logLevel = ANDROID_LOG_INFO; + else if (type & VC_CONTAINER_LOG_DEBUG) + logLevel = ANDROID_LOG_DEBUG; + + // Actually put the message out. + LOG_PRI_VA(logLevel, LOG_TAG, format, args); + } +#else +#ifndef ENABLE_CONTAINERS_STANDALONE + vcos_vlog(format, args); +#else + vprintf(format, args); printf("\n"); + fflush(0); +#endif +#endif +} diff --git a/containers/core/containers_logging.h b/containers/core/containers_logging.h new file mode 100644 index 0000000..a7c2bd9 --- /dev/null +++ b/containers/core/containers_logging.h @@ -0,0 +1,77 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_LOGGING_H +#define VC_CONTAINERS_LOGGING_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file containers_logging.h + * Logging API used by container readers and writers + */ + +typedef enum { + VC_CONTAINER_LOG_ERROR = 0x01, + VC_CONTAINER_LOG_INFO = 0x02, + VC_CONTAINER_LOG_DEBUG = 0x04, + VC_CONTAINER_LOG_FORMAT = 0x08, + VC_CONTAINER_LOG_ALL = 0xFF +} VC_CONTAINER_LOG_TYPE_T; + +void vc_container_log(VC_CONTAINER_T *ctx, VC_CONTAINER_LOG_TYPE_T type, const char *format, ...); +void vc_container_log_vargs(VC_CONTAINER_T *ctx, VC_CONTAINER_LOG_TYPE_T type, const char *format, va_list args); +void vc_container_log_set_verbosity(VC_CONTAINER_T *ctx, uint32_t mask); +void vc_container_log_set_default_verbosity(uint32_t mask); +uint32_t vc_container_log_get_default_verbosity(void); + +#define ENABLE_CONTAINER_LOG_ERROR +#define ENABLE_CONTAINER_LOG_INFO + +#ifdef ENABLE_CONTAINER_LOG_DEBUG +# define LOG_DEBUG(ctx, ...) vc_container_log(ctx, VC_CONTAINER_LOG_DEBUG, __VA_ARGS__) +#else +# define LOG_DEBUG(ctx, ...) VC_CONTAINER_PARAM_UNUSED(ctx) +#endif + +#ifdef ENABLE_CONTAINER_LOG_ERROR +# define LOG_ERROR(ctx, ...) vc_container_log(ctx, VC_CONTAINER_LOG_ERROR, __VA_ARGS__) +#else +# define LOG_ERROR(ctx, ...) VC_CONTAINER_PARAM_UNUSED(ctx) +#endif + +#ifdef ENABLE_CONTAINER_LOG_INFO +# define LOG_INFO(ctx, ...) vc_container_log(ctx, VC_CONTAINER_LOG_INFO, __VA_ARGS__) +#else +# define LOG_INFO(ctx, ...) VC_CONTAINER_PARAM_UNUSED(ctx) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* VC_CONTAINERS_LOGGING_H */ diff --git a/containers/core/containers_private.h b/containers/core/containers_private.h new file mode 100644 index 0000000..8c379c6 --- /dev/null +++ b/containers/core/containers_private.h @@ -0,0 +1,183 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_PRIVATE_H +#define VC_CONTAINERS_PRIVATE_H + +/** \file containers_private.h + * Private interface for container readers and writers + */ + +#include +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_filters.h" +#include "containers/packetizers.h" +#include "containers/core/containers_uri.h" + +#define URI_MAX_LEN 256 + +/** \defgroup VcContainerModuleApi Container Module API + * Private interface for modules implementing container readers and writers */ +/* @{ */ + +/** Track context private to the container reader / writer instance. This private context is used to + * store data which shouldn't be exported by the public API. */ +typedef struct VC_CONTAINER_TRACK_PRIVATE_T +{ + /** Pointer to the private data of the container module in use */ + struct VC_CONTAINER_TRACK_MODULE_T *module; + + /** Pointer to the allocated buffer for the track extradata */ + uint8_t *extradata; + /** Size of the allocated buffer for the track extradata */ + uint32_t extradata_size; + + /** Pointer to the allocated buffer for the track DRM data*/ + uint8_t *drmdata; + /** Size of the allocated buffer for the track DRM data */ + uint32_t drmdata_size; + + /** Packetizer used by this track */ + VC_PACKETIZER_T *packetizer; + +} VC_CONTAINER_TRACK_PRIVATE_T; + +/** Context private to the container reader / writer instance. This private context is used to + * store data which shouldn't be exported by the public API. */ +typedef struct VC_CONTAINER_PRIVATE_T +{ + /** Pointer to the container i/o instance used to read / write to the container */ + struct VC_CONTAINER_IO_T *io; + /** Pointer to the private data of the container module in use */ + struct VC_CONTAINER_MODULE_T *module; + + /** Reads a data packet from a container reader. + * By default, the reader will read whatever packet comes next in the container and update the + * given \ref VC_CONTAINER_PACKET_T structure with this packet's information. + * This behaviour can be changed using the \ref VC_CONTAINER_READ_FLAGS_T.\n + * \ref VC_CONTAINER_READ_FLAG_INFO will instruct the reader to only return information on the + * following packet but not its actual data. The data can be retreived later by issuing another + * read request. + * \ref VC_CONTAINER_READ_FLAG_FORCE_TRACK will force the reader to read the next packet for the + * selected track (as present in the \ref VC_CONTAINER_PACKET_T structure) instead of defaulting + * to reading the packet which comes next in the container. + * \ref VC_CONTAINER_READ_FLAG_SKIP will instruct the reader to skip the next packet. In this case + * it isn't necessary for the caller to pass a pointer to a \ref VC_CONTAINER_PACKET_T structure + * unless the \ref VC_CONTAINER_READ_FLAG_INFO is also given. + * A combination of all these flags can be used. + * + * \param context Pointer to the context of the reader to use + * \param packet Pointer to the VC_CONTAINER_PACKET_T structure describing the data packet + * This needs to be partially filled before the call (buffer, buffer_size) + * \param flags Flags controlling the read operation + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_read)( VC_CONTAINER_T *context, + VC_CONTAINER_PACKET_T *packet, VC_CONTAINER_READ_FLAGS_T flags ); + + /** Writes a data packet to a container writer. + * + * \param context Pointer to the context of the writer to use + * \param packet Pointer to the VC_CONTAINER_PACKET_T structure describing the data packet + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_write)( struct VC_CONTAINER_T *context, + VC_CONTAINER_PACKET_T *packet ); + + /** Seek into a container reader. + * + * \param context Pointer to the context of the reader to use + * \param offset Offset to seek to. Used as an input as well as output value. + * \param mode Seeking mode requested. + * \param flags Flags affecting the seeking operation. + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_seek)( VC_CONTAINER_T *context, int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags); + + /** Extensible control function for container readers and writers. + * This function takes a variable number of arguments which will depend on the specific operation. + * + * \param context Pointer to the VC_CONTAINER_T context to use + * \param operation The requested operation + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_control)( VC_CONTAINER_T *context, VC_CONTAINER_CONTROL_T operation, va_list args ); + + /** Closes a container reader / writer module. + * + * \param context Pointer to the context of the instance to close + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_close)( struct VC_CONTAINER_T *context ); + + /** Pointer to container filter instance used for DRM */ + struct VC_CONTAINER_FILTER_T *drm_filter; + + /** Pointer to the container module code and symbols*/ + void *module_handle; + + /** Maximum size of a stream that is being written. + * This is set by the client using the control mechanism */ + int64_t max_size; + + /** Pointer to the temp i/o instance used to write temporary data */ + struct VC_CONTAINER_IO_T *tmp_io; + + /** Current status of the container (only used for writers to prevent trying to write + * more data if one of the writes failed) */ + VC_CONTAINER_STATUS_T status; + + /** Logging verbosity */ + uint32_t verbosity; + + /** Uniform Resource Identifier */ + struct VC_URI_PARTS_T *uri; + + /** Flag specifying whether one of the tracks is being packetized */ + bool packetizing; + + /** Temporary packet structure used to feed data to the packetizer */ + VC_CONTAINER_PACKET_T packetizer_packet; + + /** Temporary buffer used by the packetizer */ + uint8_t *packetizer_buffer; + +} VC_CONTAINER_PRIVATE_T; + +/* Internal functions */ +VC_CONTAINER_TRACK_T *vc_container_allocate_track( VC_CONTAINER_T *context, unsigned int extra_size ); +void vc_container_free_track( VC_CONTAINER_T *context, VC_CONTAINER_TRACK_T *track ); +VC_CONTAINER_STATUS_T vc_container_track_allocate_extradata( VC_CONTAINER_T *context, + VC_CONTAINER_TRACK_T *p_track, unsigned int extra_size ); +VC_CONTAINER_STATUS_T vc_container_track_allocate_drmdata( VC_CONTAINER_T *context, + VC_CONTAINER_TRACK_T *p_track, unsigned int size ); + +/* @} */ + +#endif /* VC_CONTAINERS_PRIVATE_H */ diff --git a/containers/core/containers_time.h b/containers/core/containers_time.h new file mode 100644 index 0000000..e625c34 --- /dev/null +++ b/containers/core/containers_time.h @@ -0,0 +1,103 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_TIME_H +#define VC_CONTAINERS_TIME_H + +/** \file + * Utility functions to help with timestamping of elementary stream frames + */ + +typedef struct VC_CONTAINER_TIME_T +{ + uint32_t samplerate_num; + uint32_t samplerate_den; + uint32_t time_base; + + uint32_t remainder; + + int64_t time; + +} VC_CONTAINER_TIME_T; + +/*****************************************************************************/ +STATIC_INLINE void vc_container_time_init( VC_CONTAINER_TIME_T *time, uint32_t time_base ) +{ + time->samplerate_num = 0; + time->samplerate_den = 0; + time->remainder = 0; + time->time_base = time_base; + time->time = VC_CONTAINER_TIME_UNKNOWN; +} + +/*****************************************************************************/ +STATIC_INLINE int64_t vc_container_time_get( VC_CONTAINER_TIME_T *time ) +{ + if (time->time == VC_CONTAINER_TIME_UNKNOWN || !time->samplerate_num || !time->samplerate_den) + return VC_CONTAINER_TIME_UNKNOWN; + return time->time + time->remainder * (int64_t)time->time_base * time->samplerate_den / time->samplerate_num; +} + +/*****************************************************************************/ +STATIC_INLINE void vc_container_time_set_samplerate( VC_CONTAINER_TIME_T *time, uint32_t samplerate_num, uint32_t samplerate_den ) +{ + if(time->samplerate_num == samplerate_num && + time->samplerate_den == samplerate_den) + return; + + /* We're changing samplerate, we need to reset our remainder */ + if(time->remainder) + time->time = vc_container_time_get( time ); + time->remainder = 0; + time->samplerate_num = samplerate_num; + time->samplerate_den = samplerate_den; +} + +/*****************************************************************************/ +STATIC_INLINE void vc_container_time_set( VC_CONTAINER_TIME_T *time, int64_t new_time ) +{ + if (new_time == VC_CONTAINER_TIME_UNKNOWN) + return; + time->remainder = 0; + time->time = new_time; +} + +/*****************************************************************************/ +STATIC_INLINE int64_t vc_container_time_add( VC_CONTAINER_TIME_T *time, uint32_t samples ) +{ + uint32_t increment; + + if (time->time == VC_CONTAINER_TIME_UNKNOWN || !time->samplerate_num || !time->samplerate_den) + return VC_CONTAINER_TIME_UNKNOWN; + + samples += time->remainder; + increment = samples * time->samplerate_den / time->samplerate_num; + time->time += increment * time->time_base; + time->remainder = samples - increment * time->samplerate_num / time->samplerate_den; + return vc_container_time_get(time); +} + +#endif /* VC_CONTAINERS_TIME_H */ diff --git a/containers/core/containers_uri.c b/containers/core/containers_uri.c new file mode 100644 index 0000000..b0d3c59 --- /dev/null +++ b/containers/core/containers_uri.c @@ -0,0 +1,1120 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "containers/core/containers_uri.h" + +/*****************************************************************************/ +/* Internal types and definitions */ +/*****************************************************************************/ + +typedef struct VC_URI_QUERY_T +{ + char *name; + char *value; +} VC_URI_QUERY_T; + +struct VC_URI_PARTS_T +{ + char *scheme; /**< Unescaped scheme */ + char *userinfo; /**< Unescaped userinfo */ + char *host; /**< Unescaped host name/IP address */ + char *port; /**< Unescaped port */ + char *path; /**< Unescaped path */ + char *path_extension; /**< Unescaped path extension */ + char *fragment; /**< Unescaped fragment */ + VC_URI_QUERY_T *queries; /**< Array of queries */ + uint32_t num_queries; /**< Number of queries in array */ +}; + +typedef const uint32_t *RESERVED_CHARS_TABLE_T; + +/** Reserved character table for scheme component + * Controls, space, !"#$%&'()*,/:;<=>?@[\]^`{|} and 0x7F and above reserved. */ +static uint32_t scheme_reserved_chars[8] = { + 0xFFFFFFFF, 0xFC0097FF, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/** Reserved character table for userinfo component + * Controls, space, "#%/<>?@[\]^`{|} and 0x7F and above reserved. */ +static uint32_t userinfo_reserved_chars[8] = { + 0xFFFFFFFF, 0xD000802D, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/** Reserved character table for host component + * Controls, space, "#%/<>?@\^`{|} and 0x7F and above reserved. */ +static uint32_t host_reserved_chars[8] = { + 0xFFFFFFFF, 0xD000802D, 0x50000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/** Reserved character table for port component + * Controls, space, !"#$%&'()*+,/:;<=>?@[\]^`{|} and 0x7F and above reserved. */ +static uint32_t port_reserved_chars[8] = { + 0xFFFFFFFF, 0xFC009FFF, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/** Reserved character table for path component + * Controls, space, "#%<>?[\]^`{|} and 0x7F and above reserved. */ +static uint32_t path_reserved_chars[8] = { + 0xFFFFFFFF, 0xD000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/** Reserved character table for query component + * Controls, space, "#%<>[\]^`{|} and 0x7F and above reserved. */ +static uint32_t query_reserved_chars[8] = { + 0xFFFFFFFF, 0x5000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/** Reserved character table for fragment component + * Controls, space, "#%<>[\]^`{|} and 0x7F and above reserved. */ +static uint32_t fragment_reserved_chars[8] = { + 0xFFFFFFFF, 0x5000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +#define URI_RESERVED(C, TABLE) (!!((TABLE)[(unsigned char)(C) >> 5] & (1 << ((C) & 0x1F)))) + +#define SCHEME_DELIMITERS ":/?#" +#define NETWORK_DELIMITERS "@/?#" +#define HOST_PORT_DELIMITERS "/?#" +#define PATH_DELIMITERS "?#" +#define QUERY_DELIMITERS "#" + +/*****************************************************************************/ +/* Internal functions */ +/*****************************************************************************/ + +static char to_hex(int v) +{ + if (v > 9) + return 'A' + v - 10; + return '0' + v; +} + +/*****************************************************************************/ +static uint32_t from_hex(const char *str, uint32_t str_len) +{ + uint32_t val = 0; + + while (str_len--) + { + char c = *str++; + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'A' && c <= 'F') + c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + c -= 'a' - 10; + else + c = 0; /* Illegal character (not hex) */ + val = (val << 4) + c; + } + + return val; +} + +/*****************************************************************************/ +static uint32_t escaped_length( const char *str, RESERVED_CHARS_TABLE_T reserved ) +{ + uint32_t ii; + uint32_t esclen = 0; + char c; + + for (ii = strlen(str); ii > 0; ii--) + { + c = *str++; + if (URI_RESERVED(c, reserved)) + { + /* Reserved character needs escaping as %xx */ + esclen += 3; + } else { + esclen++; + } + } + + return esclen; +} + +/*****************************************************************************/ +static uint32_t escape_string( const char *str, char *escaped, + RESERVED_CHARS_TABLE_T reserved ) +{ + uint32_t ii; + uint32_t esclen = 0; + + if (!str) + return 0; + + for (ii = strlen(str); ii > 0; ii--) + { + char c = *str++; + + if (URI_RESERVED(c, reserved)) + { + escaped[esclen++] = '%'; + escaped[esclen++] = to_hex((c >> 4) & 0xF); + escaped[esclen++] = to_hex(c & 0xF); + } else { + escaped[esclen++] = c; + } + } + + return esclen; +} + +/*****************************************************************************/ +static uint32_t unescaped_length( const char *str, uint32_t str_len ) +{ + uint32_t ii; + uint32_t unesclen = 0; + + for (ii = 0; ii < str_len; ii++) + { + if (*str++ == '%' && (ii + 2) < str_len) + { + str += 2; /* Should be two hex values next */ + ii += 2; + } + unesclen++; + } + + return unesclen; +} + +/*****************************************************************************/ +static void unescape_string( const char *str, uint32_t str_len, char *unescaped ) +{ + uint32_t ii; + + for (ii = 0; ii < str_len; ii++) + { + char c = *str++; + + if (c == '%' && (ii + 2) < str_len ) + { + c = (char)(from_hex(str, 2) & 0xFF); + str += 2; + ii += 2; + } + *unescaped++ = c; + } + + *unescaped = '\0'; +} + +/*****************************************************************************/ +static char *create_unescaped_string( const char *escstr, uint32_t esclen ) +{ + char *unescstr; + + unescstr = (char *)malloc(unescaped_length(escstr, esclen) + 1); /* Allow for NUL */ + if (unescstr) + unescape_string(escstr, esclen, unescstr); + + return unescstr; +} + +/*****************************************************************************/ +static bool duplicate_string( const char *src, char **p_dst ) +{ + if (*p_dst) + free(*p_dst); + + if (src) + { + size_t str_size = strlen(src) + 1; + + *p_dst = (char *)malloc(str_size); + if (!*p_dst) + return false; + + memcpy(*p_dst, src, str_size); + } else + *p_dst = NULL; + + return true; +} + +/*****************************************************************************/ +static void release_string( char **str ) +{ + if (*str) + { + free(*str); + *str = NULL; + } +} + +/*****************************************************************************/ +static void to_lower_string( char *str ) +{ + char c; + + while ((c = *str) != '\0') + { + if (c >= 'A' && c <= 'Z') + *str = c - 'A' + 'a'; + str++; + } +} + +/*****************************************************************************/ +static const char *vc_uri_find_delimiter(const char *str, const char *delimiters) +{ + const char *ptr = str; + char c; + + while ((c = *ptr) != 0) + { + if (strchr(delimiters, c) != 0) + break; + ptr++; + } + + return ptr; +} + +/*****************************************************************************/ +static void vc_uri_set_path_extension(VC_URI_PARTS_T *p_uri) +{ + char *end; + + if (!p_uri) + return; + + p_uri->path_extension = NULL; + + if (!p_uri->path) + return; + + /* Look for the magic dot */ + for (end = p_uri->path + strlen(p_uri->path); *end != '.'; end--) + if (end == p_uri->path || *end == '/' || *end == '\\') + return; + + p_uri->path_extension = end + 1; +} + +/*****************************************************************************/ +static bool parse_authority( VC_URI_PARTS_T *p_uri, const char *str, + uint32_t str_len, const char *userinfo_end ) +{ + const char *marker = userinfo_end; + const char *str_end = str + str_len; + char c; + + if (marker) + { + p_uri->userinfo = create_unescaped_string(str, marker - str); + if (!p_uri->userinfo) + return false; + str = marker + 1; /* Past '@' character */ + } + + if (*str == '[') /* IPvFuture / IPv6 address */ + { + /* Find end of address marker */ + for (marker = str; marker < str_end; marker++) + { + c = *marker; + if (c == ']') + break; + } + + if (marker < str_end) + marker++; /* Found marker, move to next character */ + } else { + /* Find port value marker*/ + for (marker = str; marker < str_end; marker++) + { + c = *marker; + if (c == ':') + break; + } + } + + /* Always store the host, even if empty, to trigger the "://" form of URI */ + p_uri->host = create_unescaped_string(str, marker - str); + if (!p_uri->host) + return false; + to_lower_string(p_uri->host); /* Host names are case-insensitive */ + + if (*marker == ':') + { + str = marker + 1; + p_uri->port = create_unescaped_string(str, str_end - str); + if (!p_uri->port) + return false; + } + + return true; +} + +/*****************************************************************************/ +static bool store_query( VC_URI_PARTS_T *p_uri, const char *name_start, + const char *equals_ptr, const char *query_end) +{ + uint32_t name_len, value_len; + + if (equals_ptr) + { + name_len = equals_ptr - name_start; + value_len = query_end - equals_ptr - 1; /* Don't include '=' itself */ + } else { + name_len = query_end - name_start; + value_len = 0; + } + + /* Only store something if there is a name */ + if (name_len) + { + char *name, *value = NULL; + VC_URI_QUERY_T *p_query; + + if (equals_ptr) + { + value = create_unescaped_string(equals_ptr + 1, value_len); + if (!value) + return false; + equals_ptr = query_end; + } + + name = create_unescaped_string(name_start, name_len); + if (!name) + { + if (value) + free(value); + return false; + } + + /* Store query data in URI structure */ + p_query = &p_uri->queries[ p_uri->num_queries++ ]; + p_query->name = name; + p_query->value = value; + } + + return true; +} + +/*****************************************************************************/ +static bool parse_query( VC_URI_PARTS_T *p_uri, const char *str, uint32_t str_len ) +{ + uint32_t ii; + uint32_t query_count; + VC_URI_QUERY_T *queries; + const char *name_start = str; + const char *equals_ptr = NULL; + char c; + + if (!str_len) + return true; + + /* Scan for the number of query items, so array can be allocated the right size */ + query_count = 1; /* At least */ + for (ii = 0; ii < str_len; ii++) + { + c = str[ii]; + + if (c == '&' || c ==';') + query_count++; + } + + queries = (VC_URI_QUERY_T *)malloc(query_count * sizeof(VC_URI_QUERY_T)); + if (!queries) + return false; + + p_uri->queries = queries; + + /* Go back and parse the string for each query item and store in array */ + for (ii = 0; ii < str_len; ii++) + { + c = *str; + + /* Take first '=' as break between name and value */ + if (c == '=' && !equals_ptr) + equals_ptr = str; + + /* If at the end of the name or name/value pair */ + if (c == '&' || c ==';') + { + if (!store_query(p_uri, name_start, equals_ptr, str)) + return false; + + equals_ptr = NULL; + name_start = str + 1; + } + + str++; + } + + return store_query(p_uri, name_start, equals_ptr, str); +} + +/*****************************************************************************/ +static uint32_t calculate_uri_length(const VC_URI_PARTS_T *p_uri) +{ + uint32_t length = 0; + uint32_t count; + + /* With no scheme, assume this is a plain path (without escaping) */ + if (!p_uri->scheme) + return p_uri->path ? strlen(p_uri->path) : 0; + + length += escaped_length(p_uri->scheme, scheme_reserved_chars); + length++; /* for the colon */ + + if (p_uri->host) + { + length += escaped_length(p_uri->host, host_reserved_chars) + 2; /* for the double slash */ + if (p_uri->userinfo) + length += escaped_length(p_uri->userinfo, userinfo_reserved_chars) + 1; /* for the '@' */ + if (p_uri->port) + length += escaped_length(p_uri->port, port_reserved_chars) + 1; /* for the ':' */ + } + + if (p_uri->path) + length += escaped_length(p_uri->path, path_reserved_chars); + + count = p_uri->num_queries; + if (count) + { + VC_URI_QUERY_T * queries = p_uri->queries; + + while (count--) + { + /* The name is preceded by either the '?' or the '&' */ + length += escaped_length(queries->name, query_reserved_chars) + 1; + + /* The value is optional, but if present will require an '=' */ + if (queries->value) + length += escaped_length(queries->value, query_reserved_chars) + 1; + queries++; + } + } + + if (p_uri->fragment) + length += escaped_length(p_uri->fragment, fragment_reserved_chars) + 1; /* for the '#' */ + + return length; +} + +/*****************************************************************************/ +static void build_uri(const VC_URI_PARTS_T *p_uri, char *buffer, size_t buffer_size) +{ + uint32_t count; + + /* With no scheme, assume this is a plain path (without escaping) */ + if (!p_uri->scheme) + { + if (p_uri->path) + strncpy(buffer, p_uri->path, buffer_size); + else + buffer[0] = '\0'; + return; + } + + buffer += escape_string(p_uri->scheme, buffer, scheme_reserved_chars); + *buffer++ = ':'; + + if (p_uri->host) + { + *buffer++ = '/'; + *buffer++ = '/'; + if (p_uri->userinfo) + { + buffer += escape_string(p_uri->userinfo, buffer, userinfo_reserved_chars); + *buffer++ = '@'; + } + buffer += escape_string(p_uri->host, buffer, host_reserved_chars); + if (p_uri->port) + { + *buffer++ = ':'; + buffer += escape_string(p_uri->port, buffer, port_reserved_chars); + } + } + + if (p_uri->path) + buffer += escape_string(p_uri->path, buffer, path_reserved_chars); + + count = p_uri->num_queries; + if (count) + { + VC_URI_QUERY_T * queries = p_uri->queries; + + *buffer++ = '?'; + while (count--) + { + buffer += escape_string(queries->name, buffer, query_reserved_chars); + + if (queries->value) + { + *buffer++ = '='; + buffer += escape_string(queries->value, buffer, query_reserved_chars); + } + + /* Add separator if there is another item to add */ + if (count) + *buffer++ = '&'; + + queries++; + } + } + + if (p_uri->fragment) + { + *buffer++ = '#'; + buffer += escape_string(p_uri->fragment, buffer, fragment_reserved_chars); + } + + *buffer = '\0'; +} + +/*****************************************************************************/ +static bool vc_uri_copy_base_path( const VC_URI_PARTS_T *base_uri, + VC_URI_PARTS_T *relative_uri ) +{ + const char *base_path = vc_uri_path(base_uri); + + /* No path set (or empty), copy from base */ + if (!vc_uri_set_path(relative_uri, base_path)) + return false; + + /* If relative path has no queries, copy base queries across */ + if (!vc_uri_num_queries(relative_uri)) + { + uint32_t base_queries = vc_uri_num_queries(base_uri); + const char *name, *value; + uint32_t ii; + + for (ii = 0; ii < base_queries; ii++) + { + vc_uri_query(base_uri, ii, &name, &value); + if (!vc_uri_add_query(relative_uri, name, value)) + return false; + } + } + + return true; +} + +/*****************************************************************************/ +static void vc_uri_remove_single_dot_segments( char *path_str ) +{ + char *slash = path_str - 1; + + while (slash++) + { + if (*slash == '.') + { + switch (slash[1]) + { + case '/': /* Single dot segment, remove it */ + memmove(slash, slash + 2, strlen(slash + 2) + 1); + break; + case '\0': /* Trailing single dot, remove it */ + *slash = '\0'; + break; + default: /* Something else (e.g. ".." or ".foo") */ + ; /* Do nothing */ + } + } + slash = strchr(slash, '/'); + } +} + +/*****************************************************************************/ +static void vc_uri_remove_double_dot_segments( char *path_str ) +{ + char *previous_segment = path_str; + char *slash; + + if (previous_segment[0] == '/') + previous_segment++; + + /* Remove strings of the form "/../" (or "/.." at the end of the path) + * as long as is not itself ".." */ + slash = strchr(previous_segment, '/'); + while (slash) + { + if (previous_segment[0] != '.' || previous_segment[1] != '.' || previous_segment[2] != '/') + { + if (slash[1] == '.' && slash[2] == '.') + { + bool previous_segment_removed = true; + + switch (slash[3]) + { + case '/': /* "/../" inside path, snip it and last segment out */ + memmove(previous_segment, slash + 4, strlen(slash + 4) + 1); + break; + case '\0': /* Trailing "/.." on path, just terminate path at last segment */ + *previous_segment = '\0'; + break; + default: /* Not a simple ".." segment, so skip over it */ + previous_segment_removed = false; + } + + if (previous_segment_removed) + { + /* The segment just removed was the first one in the path (optionally + * prefixed by a slash), so no more can be removed: stop. */ + if (previous_segment < path_str + 2) + break; + + /* Move back to slash before previous segment, or the start of the path */ + slash = previous_segment - 1; + while (--slash >= path_str && *slash != '/') + ; /* Everything done in the while */ + } + } + } + previous_segment = slash + 1; + slash = strchr(previous_segment, '/'); + } +} + +/*****************************************************************************/ +/* API functions */ +/*****************************************************************************/ + +VC_URI_PARTS_T *vc_uri_create( void ) +{ + VC_URI_PARTS_T *p_uri; + + p_uri = (VC_URI_PARTS_T *)malloc(sizeof(VC_URI_PARTS_T)); + if (p_uri) + { + memset(p_uri, 0, sizeof(VC_URI_PARTS_T)); + } + + return p_uri; +} + +/*****************************************************************************/ +void vc_uri_clear( VC_URI_PARTS_T *p_uri ) +{ + if (!p_uri) + return; + + release_string(&p_uri->scheme); + release_string(&p_uri->userinfo); + release_string(&p_uri->host); + release_string(&p_uri->port); + release_string(&p_uri->path); + release_string(&p_uri->fragment); + + if (p_uri->queries) + { + VC_URI_QUERY_T *queries = p_uri->queries; + uint32_t count = p_uri->num_queries; + + while (count--) + { + release_string(&queries[count].name); + release_string(&queries[count].value); + } + + free(queries); + p_uri->queries = NULL; + p_uri->num_queries = 0; + } +} + +/*****************************************************************************/ +void vc_uri_release( VC_URI_PARTS_T *p_uri ) +{ + if (!p_uri) + return; + + vc_uri_clear(p_uri); + + free(p_uri); +} + +/*****************************************************************************/ +bool vc_uri_parse( VC_URI_PARTS_T *p_uri, const char *uri ) +{ + const char *marker; + uint32_t len; + + if (!p_uri || !uri) + return false; + + vc_uri_clear(p_uri); + + /* URI = scheme ":" hier_part [ "?" query ] [ "#" fragment ] */ + + /* Find end of scheme, or another separator */ + marker = vc_uri_find_delimiter(uri, SCHEME_DELIMITERS); + + if (*marker == ':') + { + len = (marker - uri); + if (isalpha((int)*uri) && len == 1 && marker[1] == '\\') + { + /* Looks like a bare, absolute DOS/Windows filename with a drive letter */ + /* coverity[double_free] Pointer freed and set to NULL */ + bool ret = duplicate_string(uri, &p_uri->path); + vc_uri_set_path_extension(p_uri); + return ret; + } + + p_uri->scheme = create_unescaped_string(uri, len); + if (!p_uri->scheme) + goto error; + + to_lower_string(p_uri->scheme); /* Schemes should be handled case-insensitively */ + uri = marker + 1; + } + + if (uri[0] == '/' && uri[1] == '/') /* hier-part includes authority */ + { + const char *userinfo_end = NULL; + + /* authority = [ userinfo "@" ] host [ ":" port ] */ + uri += 2; + + marker = vc_uri_find_delimiter(uri, NETWORK_DELIMITERS); + if (*marker == '@') + { + userinfo_end = marker; + marker = vc_uri_find_delimiter(marker + 1, HOST_PORT_DELIMITERS); + } + + if (!parse_authority(p_uri, uri, marker - uri, userinfo_end)) + goto error; + uri = marker; + } + + /* path */ + marker = vc_uri_find_delimiter(uri, PATH_DELIMITERS); + len = marker - uri; + if (len) + { + p_uri->path = create_unescaped_string(uri, len); + vc_uri_set_path_extension(p_uri); + if (!p_uri->path) + goto error; + } + + /* query */ + if (*marker == '?') + { + uri = marker + 1; + marker = vc_uri_find_delimiter(uri, QUERY_DELIMITERS); + if (!parse_query(p_uri, uri, marker - uri)) + goto error; + } + + /* fragment */ + if (*marker == '#') + { + uri = marker + 1; + p_uri->fragment = create_unescaped_string(uri, strlen(uri)); + if (!p_uri->fragment) + goto error; + } + + return true; + +error: + vc_uri_clear(p_uri); + return false; +} + +/*****************************************************************************/ +uint32_t vc_uri_build( const VC_URI_PARTS_T *p_uri, char *buffer, size_t buffer_size ) +{ + uint32_t required_length; + + if (!p_uri) + return 0; + + required_length = calculate_uri_length(p_uri); + if (buffer && required_length < buffer_size) /* Allow for NUL */ + build_uri(p_uri, buffer, buffer_size); + + return required_length; +} + +/*****************************************************************************/ +const char *vc_uri_scheme( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->scheme : NULL; +} + +/*****************************************************************************/ +const char *vc_uri_userinfo( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->userinfo : NULL; +} + +/*****************************************************************************/ +const char *vc_uri_host( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->host : NULL; +} + +/*****************************************************************************/ +const char *vc_uri_port( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->port : NULL; +} + +/*****************************************************************************/ +const char *vc_uri_path( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->path : NULL; +} + +/*****************************************************************************/ +const char *vc_uri_path_extension( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->path_extension : NULL; +} + +/*****************************************************************************/ +const char *vc_uri_fragment( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->fragment : NULL; +} + +/*****************************************************************************/ +uint32_t vc_uri_num_queries( const VC_URI_PARTS_T *p_uri ) +{ + return p_uri ? p_uri->num_queries : 0; +} + +/*****************************************************************************/ +void vc_uri_query( const VC_URI_PARTS_T *p_uri, uint32_t index, const char **p_name, const char **p_value ) +{ + const char *name = NULL; + const char *value = NULL; + + if (p_uri) + { + if (index < p_uri->num_queries) + { + name = p_uri->queries[index].name; + value = p_uri->queries[index].value; + } + } + + if (p_name) + *p_name = name; + if (p_value) + *p_value = value; +} + +/*****************************************************************************/ +bool vc_uri_find_query( VC_URI_PARTS_T *p_uri, uint32_t *p_index, const char *name, const char **p_value ) +{ + unsigned int i = p_index ? *p_index : 0; + + if (!p_uri) + return false; + + for (; name && i < p_uri->num_queries; i++) + { + if (!strcmp(name, p_uri->queries[i].name)) + { + if (p_value) + *p_value = p_uri->queries[i].value; + if (p_index) + *p_index = i; + return true; + } + } + + return false; +} + +/*****************************************************************************/ +bool vc_uri_set_scheme( VC_URI_PARTS_T *p_uri, const char *scheme ) +{ + return p_uri ? duplicate_string(scheme, &p_uri->scheme) : false; +} + +/*****************************************************************************/ +bool vc_uri_set_userinfo( VC_URI_PARTS_T *p_uri, const char *userinfo ) +{ + return p_uri ? duplicate_string(userinfo, &p_uri->userinfo) : false; +} + +/*****************************************************************************/ +bool vc_uri_set_host( VC_URI_PARTS_T *p_uri, const char *host ) +{ + return p_uri ? duplicate_string(host, &p_uri->host) : false; +} + +/*****************************************************************************/ +bool vc_uri_set_port( VC_URI_PARTS_T *p_uri, const char *port ) +{ + return p_uri ? duplicate_string(port, &p_uri->port) : false; +} + +/*****************************************************************************/ +bool vc_uri_set_path( VC_URI_PARTS_T *p_uri, const char *path ) +{ + bool ret = p_uri ? duplicate_string(path, &p_uri->path) : false; + vc_uri_set_path_extension(p_uri); + return ret; +} + +/*****************************************************************************/ +bool vc_uri_set_fragment( VC_URI_PARTS_T *p_uri, const char *fragment ) +{ + return p_uri ? duplicate_string(fragment, &p_uri->fragment) : false; +} + +/*****************************************************************************/ +bool vc_uri_add_query( VC_URI_PARTS_T *p_uri, const char *name, const char *value ) +{ + VC_URI_QUERY_T *queries; + uint32_t count; + + if (!p_uri || !name) + return false; + + count = p_uri->num_queries; + if (p_uri->queries) + queries = (VC_URI_QUERY_T *)realloc(p_uri->queries, (count + 1) * sizeof(VC_URI_QUERY_T)); + else + queries = (VC_URI_QUERY_T *)malloc(sizeof(VC_URI_QUERY_T)); + + if (!queries) + return false; + + /* Always store the pointer, in case it has changed, and even if we fail to copy name/value */ + p_uri->queries = queries; + queries[count].name = NULL; + queries[count].value = NULL; + + if (duplicate_string(name, &queries[count].name)) + { + if (duplicate_string(value, &queries[count].value)) + { + /* Successful exit path */ + p_uri->num_queries++; + return true; + } + + release_string(&queries[count].name); + } + + return false; +} + +/*****************************************************************************/ +bool vc_uri_merge( const VC_URI_PARTS_T *base_uri, VC_URI_PARTS_T *relative_uri ) +{ + bool success = true; + const char *relative_path; + + /* If scheme is already set, the URI is already absolute */ + if (relative_uri->scheme) + return true; + + /* Otherwise, copy the base scheme */ + if (!duplicate_string(base_uri->scheme, &relative_uri->scheme)) + return false; + + /* If any of the network info is set, use the rest of the relative URI as-is */ + if (relative_uri->host || relative_uri->port || relative_uri->userinfo) + return true; + + /* Otherwise, copy the base network info */ + if (!duplicate_string(base_uri->host, &relative_uri->host) || + !duplicate_string(base_uri->port, &relative_uri->port) || + !duplicate_string(base_uri->userinfo, &relative_uri->userinfo)) + return false; + + relative_path = relative_uri->path; + + if (!relative_path || !*relative_path) + { + /* No relative path (could be queries and/or fragment), so take base path */ + success = vc_uri_copy_base_path(base_uri, relative_uri); + } + else if (*relative_path != '/') + { + const char *base_path = base_uri->path; + char *merged_path; + char *slash; + size_t len; + + /* Path is relative, merge in with base path */ + if (!base_path || !*base_path) + { + if (relative_uri->host || relative_uri->port || relative_uri->userinfo) + base_path = "/"; /* Need a separator to split network info from path */ + else + base_path = ""; + } + + len = strlen(base_path) + strlen(relative_path) + 1; + + /* Allocate space for largest possible combined path */ + merged_path = (char *)malloc(len); + if (!merged_path) + return false; + + strncpy(merged_path, base_path, len); + + slash = strrchr(merged_path, '/'); /* Note: reverse search */ + if (*relative_path == ';') + { + char *semi; + + /* Relative path is just parameters, so remove any base parameters in final segment */ + if (!slash) + slash = merged_path; + semi = strchr(slash, ';'); + if (semi) + semi[0] = '\0'; + } else { + /* Remove final segment */ + if (slash) + slash[1] = '\0'; + else + merged_path[0] = '\0'; + } + strncat(merged_path, relative_path, len - strlen(merged_path) - 1); + + vc_uri_remove_single_dot_segments(merged_path); + vc_uri_remove_double_dot_segments(merged_path); + + success = duplicate_string(merged_path, &relative_uri->path); + + free(merged_path); + } + /* Otherwise path is absolute, which can be left as-is */ + + return success; +} diff --git a/containers/core/containers_uri.h b/containers/core/containers_uri.h new file mode 100644 index 0000000..c4e8cc7 --- /dev/null +++ b/containers/core/containers_uri.h @@ -0,0 +1,241 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VC_CONTAINERS_URI_H +#define VC_CONTAINERS_URI_H + +/** \file containers_uri.h + * API for parsing and building URI strings as described in RFC3986. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "containers/containers.h" + +typedef struct VC_URI_PARTS_T VC_URI_PARTS_T; + +/** Create an empty URI structure. + * + * \return The new URI structure. */ +VC_URI_PARTS_T *vc_uri_create( void ); + +/** Destroy a URI structure. + * + * \param p_uri Pointer to a URI parts structure. */ +void vc_uri_release( VC_URI_PARTS_T *p_uri ); + +/** Clear a URI structure. + * Any URI component strings held are released, but the structure itself is not. + * + * \param p_uri Pointer to a URI parts structure. */ +void vc_uri_clear( VC_URI_PARTS_T *p_uri ); + +/** Parses and unescapes a URI into the component parts. + * + * \param p_uri Pointer to a URI parts structure. + * \param uri Pointer to a URI string to be parsed. + * \return True if successful, false if not. */ +bool vc_uri_parse( VC_URI_PARTS_T *p_uri, const char *uri ); + +/** Builds the URI component parts into a URI string. + * If buffer is NULL, or buffer_size is too small, nothing is written to the + * buffer but the required string length is still returned. buffer_size must be + * at least one more than the value returned. + * + * \param p_uri Pointer to a URI parts structure. + * \param buffer Pointer to where the URI string is to be built, or NULL. + * \param buffer_size Number of bytes available in the buffer. + * \return The length of the URI string. */ +uint32_t vc_uri_build( const VC_URI_PARTS_T *p_uri, char *buffer, size_t buffer_size ); + +/** Retrieves the scheme of the URI. + * The string is valid until either the scheme is changed or the URI is released. + * + * \param p_uri The parsed URI. + * \return Pointer to the scheme string. */ +const char *vc_uri_scheme( const VC_URI_PARTS_T *p_uri ); + +/** Retrieves the userinfo of the URI. + * The string is valid until either the userinfo is changed or the URI is released. + * + * \param p_uri The parsed URI. + * \return Pointer to the userinfo string. */ +const char *vc_uri_userinfo( const VC_URI_PARTS_T *p_uri ); + +/** Retrieves the host of the URI. + * The string is valid until either the host is changed or the URI is released. + * + * \param p_uri The parsed URI. + * \return Pointer to the host string. */ +const char *vc_uri_host( const VC_URI_PARTS_T *p_uri ); + +/** Retrieves the port of the URI. + * The string is valid until either the port is changed or the URI is released. + * + * \param p_uri The parsed URI. + * \return Pointer to the port string. */ +const char *vc_uri_port( const VC_URI_PARTS_T *p_uri ); + +/** Retrieves the path of the URI. + * The string is valid until either the path is changed or the URI is released. + * + * \param p_uri The parsed URI. + * \return Pointer to the path string. */ +const char *vc_uri_path( const VC_URI_PARTS_T *p_uri ); + +/** Retrieves the extension part of the path of the URI. + * The string is valid until either the path is changed or the URI is released. + * + * \param p_uri The parsed URI. + * \return Pointer to the extension string. */ +const char *vc_uri_path_extension( const VC_URI_PARTS_T *p_uri ); + +/** Retrieves the fragment of the URI. + * The string is valid until either the fragment is changed or the URI is released. + * + * \param p_uri The parsed URI. + * \return Pointer to the fragment string. */ +const char *vc_uri_fragment( const VC_URI_PARTS_T *p_uri ); + +/** Returns the number of query name/value pairs stored. + * + * \param p_uri The parsed URI. + * \return Number of queries stored. */ +uint32_t vc_uri_num_queries( const VC_URI_PARTS_T *p_uri ); + +/** Retrieves a given query's name and value + * If either p_name or p_value are NULL, that part of the query is not returned, + * otherwise it is set to the address of the string (which may itself be NULL). + * + * \param p_uri The parsed URI. + * \param index Selects the query to get. + * \param p_name Address of a string pointer to receive query name, or NULL. + * \param p_value Address of a string pointer to receive query value, or NULL. */ +void vc_uri_query( const VC_URI_PARTS_T *p_uri, uint32_t index, const char **p_name, const char **p_value ); + +/** Finds a specific query in the array. + * If p_index is NULL, then it is assumed the search should start at index 0, + * otherwise the search will start at the specified index and the index will + * be updated on return to point to the query which has been found. + * If p_value is NULL, that part of the query is not returned, + * otherwise it is set to the address of the value string (which may itself be NULL). + * + * \param p_uri Pointer to a URI parts structure. + * \param p_index Index from which to start the search. May be NULL. + * \param name Unescaped query name. + * \param value Unescaped query value. May be NULL. + * \return True if successful, false if not. */ +bool vc_uri_find_query( VC_URI_PARTS_T *p_uri, uint32_t *p_index, const char *name, const char **p_value ); + +/** Sets the scheme of the URI. + * The string will be copied and stored in the URI, releasing and replacing + * any existing string. If NULL is passed, any existing string shall simply be + * released. + * + * \param p_uri The parsed URI. + * \param scheme Pointer to the new scheme string, or NULL. + * \return True if succesful, false on memory allocation failure. */ +bool vc_uri_set_scheme( VC_URI_PARTS_T *p_uri, const char *scheme ); + +/** Sets the userinfo of the URI. + * The string will be copied and stored in the URI, releasing and replacing + * any existing string. If NULL is passed, any existing string shall simply be + * released. + * + * \param p_uri The parsed URI. + * \param userinfo Pointer to the new userinfo string, or NULL. + * \return True if succesful, false on memory allocation failure. */ +bool vc_uri_set_userinfo( VC_URI_PARTS_T *p_uri, const char *userinfo ); + +/** Sets the host of the URI. + * The string will be copied and stored in the URI, releasing and replacing + * any existing string. If NULL is passed, any existing string shall simply be + * released. + * + * \param p_uri The parsed URI. + * \param host Pointer to the new host string, or NULL. + * \return True if succesful, false on memory allocation failure. */ +bool vc_uri_set_host( VC_URI_PARTS_T *p_uri, const char *host ); + +/** Sets the port of the URI. + * The string will be copied and stored in the URI, releasing and replacing + * any existing string. If NULL is passed, any existing string shall simply be + * released. + * + * \param p_uri The parsed URI. + * \param port Pointer to the new port string, or NULL. + * \return True if succesful, false on memory allocation failure. */ +bool vc_uri_set_port( VC_URI_PARTS_T *p_uri, const char *port ); + +/** Sets the path of the URI. + * The string will be copied and stored in the URI, releasing and replacing + * any existing string. If NULL is passed, any existing string shall simply be + * released. + * + * \param p_uri The parsed URI. + * \param path Pointer to the new path string, or NULL. + * \return True if succesful, false on memory allocation failure. */ +bool vc_uri_set_path( VC_URI_PARTS_T *p_uri, const char *path ); + +/** Sets the fragment of the URI. + * The string will be copied and stored in the URI, releasing and replacing + * any existing string. If NULL is passed, any existing string shall simply be + * released. + * + * \param p_uri The parsed URI. + * \param fragment Pointer to the new fragment string, or NULL. + * \return True if succesful, false on memory allocation failure. */ +bool vc_uri_set_fragment( VC_URI_PARTS_T *p_uri, const char *fragment ); + +/** Adds an query to the array. + * Note that the queries pointer may change after this function is called. + * May fail due to memory allocation failure or invalid parameters. + * + * \param p_uri Pointer to a URI parts structure. + * \param name Unescaped query name. + * \param value Unescaped query value. May be NULL. + * \return True if successful, false if not. */ +bool vc_uri_add_query( VC_URI_PARTS_T *p_uri, const char *name, const char *value ); + +/** Merge a base URI and a relative URI. + * In general, where the relative URI does not have a given element, the + * corresponding element from the base URI is used. See RFC1808. + * The combined URI is left in relative_uri. If the function is unsuccessful, + * the relative_uri may have been partially modified. + * + * \param base_uri Pointer to the base URI parts structure. + * \param relative_uri Pointer to the relative URI parts structure. + * \return True if successful, false if not. */ +bool vc_uri_merge( const VC_URI_PARTS_T *base_uri, VC_URI_PARTS_T *relative_uri ); + +#ifdef __cplusplus +} +#endif + +#endif /* VC_CONTAINERS_URI_H */ diff --git a/containers/core/containers_utils.c b/containers/core/containers_utils.c new file mode 100644 index 0000000..477093f --- /dev/null +++ b/containers/core/containers_utils.c @@ -0,0 +1,366 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_utils.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define BITMAPINFOHEADER_SIZE_MAX 40 +#define MAX_EXTENSION_SIZE 4 + +#define VC_CONTAINER_ES_FORMAT_MAGIC ((uint32_t)VC_FOURCC('m','a','g','f')) +#define EXTRADATA_SIZE_DEFAULT 32 +#define EXTRADATA_SIZE_MAX (10*1024) + +/*****************************************************************************/ +typedef struct VC_CONTAINER_ES_FORMAT_PRIVATE_T +{ + VC_CONTAINER_ES_FORMAT_T format; + VC_CONTAINER_ES_SPECIFIC_FORMAT_T type; + + uint32_t magic; + + unsigned int extradata_size; + uint8_t *extradata; + + uint8_t buffer[EXTRADATA_SIZE_DEFAULT]; + +} VC_CONTAINER_ES_FORMAT_PRIVATE_T; + +/*****************************************************************************/ +VC_CONTAINER_ES_FORMAT_T *vc_container_format_create(unsigned int extradata_size) +{ + VC_CONTAINER_ES_FORMAT_PRIVATE_T *private; + VC_CONTAINER_STATUS_T status; + + private = malloc(sizeof(*private)); + if(!private) return 0; + memset(private, 0, sizeof(*private)); + + private->magic = VC_CONTAINER_ES_FORMAT_MAGIC; + private->format.type = (void *)&private->type; + private->extradata_size = EXTRADATA_SIZE_DEFAULT; + + status = vc_container_format_extradata_alloc(&private->format, extradata_size); + if(status != VC_CONTAINER_SUCCESS) + { + free(private); + return NULL; + } + + return &private->format; +} + +/*****************************************************************************/ +void vc_container_format_delete(VC_CONTAINER_ES_FORMAT_T *format) +{ + VC_CONTAINER_ES_FORMAT_PRIVATE_T *private = (VC_CONTAINER_ES_FORMAT_PRIVATE_T *)format; + vc_container_assert(private->magic == VC_CONTAINER_ES_FORMAT_MAGIC); + if(private->extradata) free(private->extradata); + free(private); +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_format_extradata_alloc( + VC_CONTAINER_ES_FORMAT_T *format, unsigned int size) +{ + VC_CONTAINER_ES_FORMAT_PRIVATE_T *private = (VC_CONTAINER_ES_FORMAT_PRIVATE_T *)format; + vc_container_assert(private->magic == VC_CONTAINER_ES_FORMAT_MAGIC); + + /* Sanity check the size requested */ + if(size > EXTRADATA_SIZE_MAX) + return VC_CONTAINER_ERROR_CORRUPTED; + + /* Allocate memory if needed */ + if(private->extradata_size < size) + { + if(private->extradata) free(private->extradata); + private->extradata = malloc(size); + if(!private->extradata) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + private->extradata_size = size; + } + + /* Set the fields in the actual format structure */ + if(private->extradata) private->format.extradata = private->extradata; + else private->format.extradata = private->buffer; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_format_copy( VC_CONTAINER_ES_FORMAT_T *p_out, + VC_CONTAINER_ES_FORMAT_T *p_in, + unsigned int extra_buffer_size) +{ + void *type = p_out->type; + uint8_t *extradata = p_out->extradata; + + /* Check we have a sufficient buffer to copy the extra data */ + if(p_in->extradata_size > extra_buffer_size || + (p_in->extradata_size && !p_out->extradata)) + return VC_CONTAINER_ERROR_BUFFER_TOO_SMALL; + + *p_out->type = *p_in->type; + *p_out = *p_in; + p_out->type = type; + p_out->extradata = extradata; + if(p_in->extradata_size) + memcpy(p_out->extradata, p_in->extradata, p_in->extradata_size); + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +int utf8_from_charset(const char *charset, char *out, unsigned int out_size, + const void *in, unsigned int in_size) +{ + unsigned int i; + const uint16_t *in16 = (const uint16_t *)in; + const uint8_t *in8 = (const uint8_t *)in; + + if(out_size < 1) return 1; + if(!strcmp(charset, "UTF16-LE")) goto utf16le; + if(!strcmp(charset, "UTF8")) goto utf8; + else return 1; + + utf16le: + for(i = 0; i < in_size / 2 && in16[i] && i < out_size - 1; i++) + { + out[i] = in16[i]; + } + out[i] = 0; + return 0; + + utf8: + for(i = 0; i < in_size && in8[i] && i < out_size - 1; i++) + { + out[i] = in8[i]; + } + out[i] = 0; + return 0; +} + +/*****************************************************************************/ +unsigned int vc_container_es_format_to_waveformatex(VC_CONTAINER_ES_FORMAT_T *format, + uint8_t *buffer, unsigned int buffer_size) +{ + uint16_t waveformat = codec_to_waveformat(format->codec); + + if(format->es_type != VC_CONTAINER_ES_TYPE_AUDIO || + waveformat == WAVE_FORMAT_UNKNOWN) return 0; + + if(!buffer) return format->extradata_size + 18; + + if(buffer_size < format->extradata_size + 18) return 0; + + /* Build a waveformatex header */ + buffer[0] = waveformat; + buffer[1] = waveformat >> 8; + buffer[2] = format->type->audio.channels; + buffer[3] = 0; + buffer[4] = (format->type->audio.sample_rate >> 0) & 0xFF; + buffer[5] = (format->type->audio.sample_rate >> 8) & 0xFF; + buffer[6] = (format->type->audio.sample_rate >> 16) & 0xFF; + buffer[7] = (format->type->audio.sample_rate >> 24) & 0xFF; + buffer[8] = (format->bitrate >> 3) & 0xFF; + buffer[9] = (format->bitrate >> 11) & 0xFF; + buffer[10] = (format->bitrate >> 19) & 0xFF; + buffer[11] = (format->bitrate >> 27) & 0xFF; + buffer[12] = (format->type->audio.block_align >> 0) & 0xFF; + buffer[13] = (format->type->audio.block_align >> 8) & 0xFF; + buffer[14] = (format->type->audio.bits_per_sample >> 0) & 0xFF; + buffer[15] = (format->type->audio.bits_per_sample >> 8) & 0xFF; + buffer[16] = (format->extradata_size >> 0) & 0xFF; + buffer[17] = (format->extradata_size >> 8) & 0xFF; + memcpy(buffer+18, format->extradata, format->extradata_size); + return format->extradata_size + 18; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_waveformatex_to_es_format(uint8_t *p, + unsigned int buffer_size, unsigned int *extra_offset, unsigned int *extra_size, + VC_CONTAINER_ES_FORMAT_T *format) +{ + VC_CONTAINER_FOURCC_T fourcc; + uint32_t waveformat_id; + + if(!p || buffer_size < 16) return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + waveformat_id = (p[1] << 8) | p[0]; + fourcc = waveformat_to_codec(waveformat_id); + + /* Read the waveformatex header */ + if(extra_offset) *extra_offset = 16; + if(extra_size) *extra_size = 0; + format->type->audio.channels = p[2]; + format->type->audio.sample_rate = (p[7] << 24) | (p[6] << 16) | (p[5] << 8) | p[4]; + format->bitrate = ((p[11] << 24) | (p[10] << 16) | (p[9] << 8) | p[8]) * 8; + format->type->audio.block_align = (p[13] << 8) | p[12]; + format->type->audio.bits_per_sample = (p[15] << 8) | p[14]; + + if(waveformat_id == WAVE_FORMAT_PCM && format->type->audio.bits_per_sample == 8) + fourcc = VC_CONTAINER_CODEC_PCM_UNSIGNED_LE; + + if(buffer_size >= 18) + { + if(extra_size) + { + *extra_size = (p[17] << 8) | p[16]; + if(*extra_size + 18 > buffer_size) *extra_size = buffer_size - 18; + } + if(extra_offset) *extra_offset = 18; + } + + /* Skip the MPEGLAYER3WAVEFORMAT structure */ + if(waveformat_id == WAVE_FORMAT_MPEGLAYER3 && extra_size) + { + if(extra_offset) *extra_offset += *extra_size; + *extra_size = 0; + } + + format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + format->codec = fourcc; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +unsigned int vc_container_es_format_to_bitmapinfoheader(VC_CONTAINER_ES_FORMAT_T *format, + uint8_t *buffer, unsigned int buffer_size) +{ + uint32_t fourcc = codec_to_vfw_fourcc(format->codec); + uint32_t size = BITMAPINFOHEADER_SIZE_MAX + format->extradata_size; + + if(format->es_type != VC_CONTAINER_ES_TYPE_VIDEO || + fourcc == VC_CONTAINER_CODEC_UNKNOWN) return 0; + + if(!buffer) return size; + if(buffer_size < size) return 0; + + /* Build a bitmapinfoheader header */ + memset(buffer, 0, BITMAPINFOHEADER_SIZE_MAX); + buffer[0] = (size >> 0) & 0xFF; + buffer[1] = (size >> 8) & 0xFF; + buffer[2] = (size >> 16) & 0xFF; + buffer[3] = (size >> 24) & 0xFF; + buffer[4] = (format->type->video.width >> 0) & 0xFF; + buffer[5] = (format->type->video.width >> 8) & 0xFF; + buffer[6] = (format->type->video.width >> 16) & 0xFF; + buffer[7] = (format->type->video.width >> 24) & 0xFF; + buffer[8] = (format->type->video.height >> 0) & 0xFF; + buffer[9] = (format->type->video.height >> 8) & 0xFF; + buffer[10] = (format->type->video.height >> 16) & 0xFF; + buffer[11] = (format->type->video.height >> 24) & 0xFF; + memcpy(buffer + 16, &fourcc, 4); + memcpy(buffer + BITMAPINFOHEADER_SIZE_MAX, format->extradata, format->extradata_size); + return size; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_bitmapinfoheader_to_es_format(uint8_t *p, + unsigned int buffer_size, unsigned int *extra_offset, unsigned int *extra_size, + VC_CONTAINER_ES_FORMAT_T *format) +{ + VC_CONTAINER_FOURCC_T fourcc; + + if(!p || buffer_size < BITMAPINFOHEADER_SIZE_MAX) return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + /* size = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]; */ + format->type->video.width = (p[7] << 24) | (p[6] << 16) | (p[5] << 8) | p[4]; + format->type->video.height = (p[11] << 24) | (p[10] << 16) | (p[9] << 8) | p[8]; + memcpy(&fourcc, p + 16, 4); + + format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + format->codec = vfw_fourcc_to_codec(fourcc); + + /* If no mapping is found from vfw, try a more generic one */ + if (format->codec == fourcc && (fourcc = fourcc_to_codec(fourcc)) != VC_CONTAINER_CODEC_UNKNOWN) + format->codec = fourcc; + + if(extra_offset) *extra_offset = BITMAPINFOHEADER_SIZE_MAX; + if(extra_size) + { + if (buffer_size > BITMAPINFOHEADER_SIZE_MAX) + *extra_size = buffer_size - BITMAPINFOHEADER_SIZE_MAX; + else + *extra_size = 0; + } + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static struct { + VC_CONTAINER_METADATA_KEY_T key; + const char *name; +} meta_key_conv[] = +{ {VC_CONTAINER_METADATA_KEY_TITLE, "title"}, + {VC_CONTAINER_METADATA_KEY_ARTIST, "artist"}, + {VC_CONTAINER_METADATA_KEY_ALBUM, "album"}, + {VC_CONTAINER_METADATA_KEY_DESCRIPTION, "description"}, + {VC_CONTAINER_METADATA_KEY_YEAR, "year"}, + {VC_CONTAINER_METADATA_KEY_GENRE, "genre"}, + {VC_CONTAINER_METADATA_KEY_TRACK, "track"}, + {VC_CONTAINER_METADATA_KEY_LYRICS, "lyrics"}, + {VC_CONTAINER_METADATA_KEY_UNKNOWN, 0} }; + +/*****************************************************************************/ +const char *vc_container_metadata_id_to_string(VC_CONTAINER_METADATA_KEY_T key) +{ + int i; + for(i = 0; meta_key_conv[i].key != VC_CONTAINER_METADATA_KEY_UNKNOWN; i++ ) + if(meta_key_conv[i].key == key) break; + return meta_key_conv[i].name; +} + +/*****************************************************************************/ +int64_t vc_container_maths_gcd(int64_t a, int64_t b) +{ + while(b != 0) + { + int64_t t = b; + b = a % b; + a = t; + } + return a; +} + +/*****************************************************************************/ +void vc_container_maths_rational_simplify(uint32_t *num, uint32_t *den) +{ + int64_t div = vc_container_maths_gcd((int64_t)*num, (int64_t)*den); + if(div) + { + *num /= div; + *den /= div; + } +} diff --git a/containers/core/containers_utils.h b/containers/core/containers_utils.h new file mode 100644 index 0000000..22349ef --- /dev/null +++ b/containers/core/containers_utils.h @@ -0,0 +1,86 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_UTILS_H +#define VC_CONTAINERS_UTILS_H + +#include "containers/containers.h" +#include "containers/containers_codecs.h" +#include "containers/core/containers_waveformat.h" + +/***************************************************************************** + * Type definitions + *****************************************************************************/ + +/** Definition of the Global Unique Identifier type as used by some containers */ +typedef struct GUID_T +{ + uint32_t word0; + uint16_t short0; + uint16_t short1; + uint8_t bytes[8]; + +} GUID_T; + +VC_CONTAINER_ES_FORMAT_T *vc_container_format_create(unsigned int extradata_size); +void vc_container_format_delete(VC_CONTAINER_ES_FORMAT_T *); +VC_CONTAINER_STATUS_T vc_container_format_extradata_alloc( + VC_CONTAINER_ES_FORMAT_T *format, unsigned int size); +VC_CONTAINER_STATUS_T vc_container_format_copy( VC_CONTAINER_ES_FORMAT_T *p_out, + VC_CONTAINER_ES_FORMAT_T *p_in, + unsigned int extra_buffer_size ); +int utf8_from_charset(const char *charset, char *out, unsigned int out_size, + const void *in, unsigned int in_size); +const char *vc_container_metadata_id_to_string(VC_CONTAINER_METADATA_KEY_T key); + +unsigned int vc_container_es_format_to_waveformatex(VC_CONTAINER_ES_FORMAT_T *format, + uint8_t *buffer, unsigned int buffer_size); +unsigned int vc_container_es_format_to_bitmapinfoheader(VC_CONTAINER_ES_FORMAT_T *format, + uint8_t *buffer, unsigned int buffer_size); +VC_CONTAINER_STATUS_T vc_container_waveformatex_to_es_format(uint8_t *p, + unsigned int buffer_size, unsigned int *extra_offset, unsigned int *extra_size, + VC_CONTAINER_ES_FORMAT_T *format); +VC_CONTAINER_STATUS_T vc_container_bitmapinfoheader_to_es_format(uint8_t *p, + unsigned int buffer_size, unsigned int *extra_offset, unsigned int *extra_size, + VC_CONTAINER_ES_FORMAT_T *format); + +/** Find the greatest common denominator of 2 numbers. + * + * @param a first number + * @param b second number + * + * @return greatest common denominator of a and b + */ +int64_t vc_container_maths_gcd(int64_t a, int64_t b); + +/** Reduce a rational number to it's simplest form. + * + * @param num Pointer to the numerator of the rational number to simplify + * @param den Pointer to the denominator of the rational number to simplify + */ +void vc_container_maths_rational_simplify(uint32_t *num, uint32_t *den); + +#endif /* VC_CONTAINERS_UTILS_H */ diff --git a/containers/core/containers_waveformat.h b/containers/core/containers_waveformat.h new file mode 100644 index 0000000..ade6b9e --- /dev/null +++ b/containers/core/containers_waveformat.h @@ -0,0 +1,180 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_WAVEFORMAT_H +#define VC_CONTAINERS_WAVEFORMAT_H + +/* WAVE form wFormatTag IDs */ +#define WAVE_FORMAT_UNKNOWN 0x0000 /* Microsoft Corporation */ +#define WAVE_FORMAT_ADPCM 0x0002 /* Microsoft Corporation */ +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 /* Microsoft Corporation */ +#define WAVE_FORMAT_VSELP 0x0004 /* Compaq Computer Corp. */ +#define WAVE_FORMAT_IBM_CVSD 0x0005 /* IBM Corporation */ +#define WAVE_FORMAT_ALAW 0x0006 /* Microsoft Corporation */ +#define WAVE_FORMAT_MULAW 0x0007 /* Microsoft Corporation */ +#define WAVE_FORMAT_DTS 0x0008 /* Microsoft Corporation */ +#define WAVE_FORMAT_DRM 0x0009 /* Microsoft Corporation */ +#define WAVE_FORMAT_WMAUDIO_VOICE 0x000A /* Microsoft Corporation */ +#define WAVE_FORMAT_OKI_ADPCM 0x0010 /* OKI */ +#define WAVE_FORMAT_DVI_ADPCM 0x0011 /* Intel Corporation */ +#define WAVE_FORMAT_IMA_ADPCM (WAVE_FORMAT_DVI_ADPCM) /* Intel Corporation */ +#define WAVE_FORMAT_MEDIASPACE_ADPCM 0x0012 /* Videologic */ +#define WAVE_FORMAT_SIERRA_ADPCM 0x0013 /* Sierra Semiconductor Corp */ +#define WAVE_FORMAT_G723_ADPCM 0x0014 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_DIGISTD 0x0015 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_DIGIFIX 0x0016 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_DIALOGIC_OKI_ADPCM 0x0017 /* Dialogic Corporation */ +#define WAVE_FORMAT_MEDIAVISION_ADPCM 0x0018 /* Media Vision, Inc. */ +#define WAVE_FORMAT_CU_CODEC 0x0019 /* Hewlett-Packard Company */ +#define WAVE_FORMAT_YAMAHA_ADPCM 0x0020 /* Yamaha Corporation of America */ +#define WAVE_FORMAT_SONARC 0x0021 /* Speech Compression */ +#define WAVE_FORMAT_DSPGROUP_TRUESPEECH 0x0022 /* DSP Group, Inc */ +#define WAVE_FORMAT_ECHOSC1 0x0023 /* Echo Speech Corporation */ +#define WAVE_FORMAT_AUDIOFILE_AF36 0x0024 /* Virtual Music, Inc. */ +#define WAVE_FORMAT_APTX 0x0025 /* Audio Processing Technology */ +#define WAVE_FORMAT_AUDIOFILE_AF10 0x0026 /* Virtual Music, Inc. */ +#define WAVE_FORMAT_PROSODY_1612 0x0027 /* Aculab plc */ +#define WAVE_FORMAT_LRC 0x0028 /* Merging Technologies S.A. */ +#define WAVE_FORMAT_DOLBY_AC2 0x0030 /* Dolby Laboratories */ +#define WAVE_FORMAT_GSM610 0x0031 /* Microsoft Corporation */ +#define WAVE_FORMAT_MSNAUDIO 0x0032 /* Microsoft Corporation */ +#define WAVE_FORMAT_ANTEX_ADPCME 0x0033 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_CONTROL_RES_VQLPC 0x0034 /* Control Resources Limited */ +#define WAVE_FORMAT_DIGIREAL 0x0035 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_DIGIADPCM 0x0036 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_CONTROL_RES_CR10 0x0037 /* Control Resources Limited */ +#define WAVE_FORMAT_NMS_VBXADPCM 0x0038 /* Natural MicroSystems */ +#define WAVE_FORMAT_CS_IMAADPCM 0x0039 /* Crystal Semiconductor IMA ADPCM */ +#define WAVE_FORMAT_ECHOSC3 0x003A /* Echo Speech Corporation */ +#define WAVE_FORMAT_ROCKWELL_ADPCM 0x003B /* Rockwell International */ +#define WAVE_FORMAT_ROCKWELL_DIGITALK 0x003C /* Rockwell International */ +#define WAVE_FORMAT_XEBEC 0x003D /* Xebec Multimedia Solutions Limited */ +#define WAVE_FORMAT_G721_ADPCM 0x0040 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_G728_CELP 0x0041 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_MSG723 0x0042 /* Microsoft Corporation */ +#define WAVE_FORMAT_PANASONIC_G726 0x0045 /* Not official Panasonic G.726 codec */ +#define WAVE_FORMAT_MPEG 0x0050 /* Microsoft Corporation */ +#define WAVE_FORMAT_RT24 0x0052 /* InSoft, Inc. */ +#define WAVE_FORMAT_PAC 0x0053 /* InSoft, Inc. */ +#define WAVE_FORMAT_MPEGLAYER3 0x0055 /* ISO/MPEG Layer3 Format Tag */ +#define WAVE_FORMAT_LUCENT_G723 0x0059 /* Lucent Technologies */ +#define WAVE_FORMAT_CIRRUS 0x0060 /* Cirrus Logic */ +#define WAVE_FORMAT_ESPCM 0x0061 /* ESS Technology */ +#define WAVE_FORMAT_VOXWARE 0x0062 /* Voxware Inc */ +#define WAVE_FORMAT_CANOPUS_ATRAC 0x0063 /* Canopus, co., Ltd. */ +#define WAVE_FORMAT_G726_ADPCM 0x0064 /* APICOM */ +#define WAVE_FORMAT_G722_ADPCM 0x0065 /* APICOM */ +#define WAVE_FORMAT_DSAT_DISPLAY 0x0067 /* Microsoft Corporation */ +#define WAVE_FORMAT_VOXWARE_BYTE_ALIGNED 0x0069 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_AC8 0x0070 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_AC10 0x0071 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_AC16 0x0072 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_AC20 0x0073 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_RT24 0x0074 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_RT29 0x0075 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_RT29HW 0x0076 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_VR12 0x0077 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_VR18 0x0078 /* Voxware Inc */ +#define WAVE_FORMAT_VOXWARE_TQ40 0x0079 /* Voxware Inc */ +#define WAVE_FORMAT_SOFTSOUND 0x0080 /* Softsound, Ltd. */ +#define WAVE_FORMAT_VOXWARE_TQ60 0x0081 /* Voxware Inc */ +#define WAVE_FORMAT_MSRT24 0x0082 /* Microsoft Corporation */ +#define WAVE_FORMAT_G729A 0x0083 /* AT&T Labs, Inc. */ +#define WAVE_FORMAT_MVI_MVI2 0x0084 /* Motion Pixels */ +#define WAVE_FORMAT_DF_G726 0x0085 /* DataFusion Systems (Pty) (Ltd) */ +#define WAVE_FORMAT_DF_GSM610 0x0086 /* DataFusion Systems (Pty) (Ltd) */ +#define WAVE_FORMAT_ISIAUDIO 0x0088 /* Iterated Systems, Inc. */ +#define WAVE_FORMAT_ONLIVE 0x0089 /* OnLive! Technologies, Inc. */ +#define WAVE_FORMAT_SBC24 0x0091 /* Siemens Business Communications Sys */ +#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 /* Sonic Foundry */ +#define WAVE_FORMAT_MEDIASONIC_G723 0x0093 /* MediaSonic */ +#define WAVE_FORMAT_PROSODY_8KBPS 0x0094 /* Aculab plc */ +#define WAVE_FORMAT_ZYXEL_ADPCM 0x0097 /* ZyXEL Communications, Inc. */ +#define WAVE_FORMAT_PHILIPS_LPCBB 0x0098 /* Philips Speech Processing */ +#define WAVE_FORMAT_PACKED 0x0099 /* Studer Professional Audio AG */ +#define WAVE_FORMAT_MALDEN_PHONYTALK 0x00A0 /* Malden Electronics Ltd. */ +#define WAVE_FORMAT_MP4A 0x00FF /* AAC */ +#define WAVE_FORMAT_RHETOREX_ADPCM 0x0100 /* Rhetorex Inc. */ +#define WAVE_FORMAT_IRAT 0x0101 /* BeCubed Software Inc. */ +#define WAVE_FORMAT_VIVO_G723 0x0111 /* Vivo Software */ +#define WAVE_FORMAT_VIVO_SIREN 0x0112 /* Vivo Software */ +#define WAVE_FORMAT_DIGITAL_G723 0x0123 /* Digital Equipment Corporation */ +#define WAVE_FORMAT_SANYO_LD_ADPCM 0x0125 /* Sanyo Electric Co., Ltd. */ +#define WAVE_FORMAT_SIPROLAB_ACEPLNET 0x0130 /* Sipro Lab Telecom Inc. */ +#define WAVE_FORMAT_SIPROLAB_ACELP4800 0x0131 /* Sipro Lab Telecom Inc. */ +#define WAVE_FORMAT_SIPROLAB_ACELP8V3 0x0132 /* Sipro Lab Telecom Inc. */ +#define WAVE_FORMAT_SIPROLAB_G729 0x0133 /* Sipro Lab Telecom Inc. */ +#define WAVE_FORMAT_SIPROLAB_G729A 0x0134 /* Sipro Lab Telecom Inc. */ +#define WAVE_FORMAT_SIPROLAB_KELVIN 0x0135 /* Sipro Lab Telecom Inc. */ +#define WAVE_FORMAT_G726ADPCM 0x0140 /* Dictaphone Corporation */ +#define WAVE_FORMAT_QUALCOMM_PUREVOICE 0x0150 /* Qualcomm, Inc. */ +#define WAVE_FORMAT_QUALCOMM_HALFRATE 0x0151 /* Qualcomm, Inc. */ +#define WAVE_FORMAT_TUBGSM 0x0155 /* Ring Zero Systems, Inc. */ +#define WAVE_FORMAT_WMAUDIO1 0x0160 /* Microsoft Corporation */ +#define WAVE_FORMAT_WMAUDIO2 0x0161 /* Microsoft Corporation */ +#define WAVE_FORMAT_WMAUDIOPRO 0x0162 /* Microsoft Corporation */ +#define WAVE_FORMAT_WMAUDIO_LOSSLESS 0x0163 /* Microsoft Corporation */ +#define WAVE_FORMAT_UNISYS_NAP_ADPCM 0x0170 /* Unisys Corp. */ +#define WAVE_FORMAT_UNISYS_NAP_ULAW 0x0171 /* Unisys Corp. */ +#define WAVE_FORMAT_UNISYS_NAP_ALAW 0x0172 /* Unisys Corp. */ +#define WAVE_FORMAT_UNISYS_NAP_16K 0x0173 /* Unisys Corp. */ +#define WAVE_FORMAT_CREATIVE_ADPCM 0x0200 /* Creative Labs, Inc */ +#define WAVE_FORMAT_CREATIVE_FASTSPEECH8 0x0202 /* Creative Labs, Inc */ +#define WAVE_FORMAT_CREATIVE_FASTSPEECH10 0x0203 /* Creative Labs, Inc */ +#define WAVE_FORMAT_UHER_ADPCM 0x0210 /* UHER informatic GmbH */ +#define WAVE_FORMAT_QUARTERDECK 0x0220 /* Quarterdeck Corporation */ +#define WAVE_FORMAT_ILINK_VC 0x0230 /* I-link Worldwide */ +#define WAVE_FORMAT_RAW_SPORT 0x0240 /* Aureal Semiconductor */ +#define WAVE_FORMAT_ESST_AC3 0x0241 /* ESS Technology, Inc. */ +#define WAVE_FORMAT_IPI_HSX 0x0250 /* Interactive Products, Inc. */ +#define WAVE_FORMAT_IPI_RPELP 0x0251 /* Interactive Products, Inc. */ +#define WAVE_FORMAT_CS2 0x0260 /* Consistent Software */ +#define WAVE_FORMAT_SONY_SCX 0x0270 /* Sony Corp. */ +#define WAVE_FORMAT_FM_TOWNS_SND 0x0300 /* Fujitsu Corp. */ +#define WAVE_FORMAT_BTV_DIGITAL 0x0400 /* Brooktree Corporation */ +#define WAVE_FORMAT_QDESIGN_MUSIC 0x0450 /* QDesign Corporation */ +#define WAVE_FORMAT_VME_VMPCM 0x0680 /* AT&T Labs, Inc. */ +#define WAVE_FORMAT_TPC 0x0681 /* AT&T Labs, Inc. */ +#define WAVE_FORMAT_OLIGSM 0x1000 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLIADPCM 0x1001 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLICELP 0x1002 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLISBC 0x1003 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLIOPR 0x1004 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_LH_CODEC 0x1100 /* Lernout & Hauspie */ +#define WAVE_FORMAT_NORRIS 0x1400 /* Norris Communications, Inc. */ +#define WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS 0x1500 /* AT&T Labs, Inc. */ +#define WAVE_FORMAT_DVM 0x2000 /* FAST Multimedia AG */ +#define WAVE_FORMAT_AAC 0x706D /* AAC */ + +#if !defined(WAVE_FORMAT_EXTENSIBLE) +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE /* Microsoft */ +#endif // !defined(WAVE_FORMAT_EXTENSIBLE) + +#if !defined(WAVE_FORMAT_PCM) +#define WAVE_FORMAT_PCM 0x0001 +#endif // !defined(WAVE_FORMAT_PCM) + +#endif /* VC_CONTAINERS_WAVEFORMAT_H */ diff --git a/containers/core/containers_writer_utils.c b/containers/core/containers_writer_utils.c new file mode 100644 index 0000000..7e24ef3 --- /dev/null +++ b/containers/core/containers_writer_utils.c @@ -0,0 +1,126 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_private.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_writer_utils.h" + +#include + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T vc_container_writer_extraio_create(VC_CONTAINER_T *context, const char *uri, + VC_CONTAINER_WRITER_EXTRAIO_T *extraio) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_PARAM_UNUSED(context); + + extraio->io = vc_container_io_open( uri, VC_CONTAINER_IO_MODE_WRITE, &status ); + extraio->refcount = 0; + extraio->temp = 0; + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_writer_extraio_create_null(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *extraio) +{ + return vc_container_writer_extraio_create(context, "null://", extraio); +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_writer_extraio_create_temp(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *extraio) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + unsigned int length = strlen(context->priv->io->uri) + 5; + char *uri = malloc(length); + if(!uri) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + snprintf(uri, length, "%s.tmp", context->priv->io->uri); + status = vc_container_writer_extraio_create(context, uri, extraio); + free(uri); + extraio->temp = true; + + if(status == VC_CONTAINER_SUCCESS && !context->priv->tmp_io) + context->priv->tmp_io = extraio->io; + + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_writer_extraio_delete(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *extraio) +{ + VC_CONTAINER_STATUS_T status; + char *uri = extraio->temp ? strdup(extraio->io->uri) : 0; + + while(extraio->refcount) vc_container_writer_extraio_disable(context, extraio); + status = vc_container_io_close( extraio->io ); + + /* coverity[check_return] On failure the worst case is a file or directory is not removed */ + if(uri) remove(uri); + if(uri) free(uri); + + if(context->priv->tmp_io == extraio->io) + context->priv->tmp_io = 0; + + return status; +} + +/*****************************************************************************/ +int64_t vc_container_writer_extraio_enable(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *extraio) +{ + VC_CONTAINER_IO_T *tmp; + + if(!extraio->refcount) + { + vc_container_io_seek(extraio->io, INT64_C(0)); + tmp = context->priv->io; + context->priv->io = extraio->io; + extraio->io = tmp; + } + return extraio->refcount++; +} + +/*****************************************************************************/ +int64_t vc_container_writer_extraio_disable(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *extraio) +{ + VC_CONTAINER_IO_T *tmp; + + if(extraio->refcount) + { + extraio->refcount--; + if(!extraio->refcount) + { + tmp = context->priv->io; + context->priv->io = extraio->io; + extraio->io = tmp; + } + } + return extraio->refcount; +} diff --git a/containers/core/containers_writer_utils.h b/containers/core/containers_writer_utils.h new file mode 100644 index 0000000..e2ccf9e --- /dev/null +++ b/containers/core/containers_writer_utils.h @@ -0,0 +1,96 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_CONTAINERS_WRITER_UTILS_H +#define VC_CONTAINERS_WRITER_UTILS_H + +/** \file containers_writer_utils.h + * Helper functions and macros for container writers + */ + +#include "containers/containers.h" +#include "containers/containers_codecs.h" +#include "containers/core/containers_io_helpers.h" + +/***************************************************************************** + * Helper inline functions to write format specific structus to an i/o stream + *****************************************************************************/ + +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_write_waveformatex( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_ES_FORMAT_T *format) +{ + /* Write waveformatex structure */ + WRITE_U16(p_ctx, codec_to_waveformat(format->codec), "Codec ID"); + WRITE_U16(p_ctx, format->type->audio.channels, "Number of Channels"); + WRITE_U32(p_ctx, format->type->audio.sample_rate, "Samples per Second"); + WRITE_U32(p_ctx, format->bitrate >> 3, "Average Number of Bytes Per Second"); + WRITE_U16(p_ctx, format->type->audio.block_align, "Block Alignment"); + WRITE_U16(p_ctx, format->type->audio.bits_per_sample, "Bits Per Sample"); + WRITE_U16(p_ctx, format->extradata_size, "Codec Specific Data Size"); + WRITE_BYTES(p_ctx, format->extradata, format->extradata_size); + + return STREAM_STATUS(p_ctx); +} + +STATIC_INLINE VC_CONTAINER_STATUS_T vc_container_write_bitmapinfoheader( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_ES_FORMAT_T *format) +{ + VC_CONTAINER_FOURCC_T fourcc; + + /* Write bitmapinfoheader structure */ + WRITE_U32(p_ctx, 40, "Format Data Size"); + WRITE_U32(p_ctx, format->type->video.width, "Image Width"); + WRITE_U32(p_ctx, format->type->video.height, "Image Height"); + WRITE_U16(p_ctx, 0, "Reserved"); + WRITE_U16(p_ctx, 0, "Bits Per Pixel Count"); + fourcc = codec_to_vfw_fourcc(format->codec); + WRITE_BYTES(p_ctx, (char *)&fourcc, 4); /* Compression ID */ + LOG_FORMAT(p_ctx, "Compression ID: %4.4s", (char *)&fourcc); + WRITE_U32(p_ctx, 0, "Image Size"); + WRITE_U32(p_ctx, 0, "Horizontal Pixels Per Meter"); + WRITE_U32(p_ctx, 0, "Vertical Pixels Per Meter"); + WRITE_U32(p_ctx, 0, "Colors Used Count"); + WRITE_U32(p_ctx, 0, "Important Colors Count"); + + WRITE_BYTES(p_ctx, format->extradata, format->extradata_size); + + return STREAM_STATUS(p_ctx); +} + +/* Helper functions to create and use extra i/o */ +typedef struct VC_CONTAINER_WRITER_EXTRAIO_T { + VC_CONTAINER_IO_T *io; + unsigned int refcount; + bool temp; +} VC_CONTAINER_WRITER_EXTRAIO_T; + +VC_CONTAINER_STATUS_T vc_container_writer_extraio_create_null(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *null); +VC_CONTAINER_STATUS_T vc_container_writer_extraio_create_temp(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *null); +VC_CONTAINER_STATUS_T vc_container_writer_extraio_delete(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *null); +int64_t vc_container_writer_extraio_enable(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *null); +int64_t vc_container_writer_extraio_disable(VC_CONTAINER_T *context, VC_CONTAINER_WRITER_EXTRAIO_T *null); + +#endif /* VC_CONTAINERS_WRITER_UTILS_H */ diff --git a/containers/core/packetizers.c b/containers/core/packetizers.c new file mode 100644 index 0000000..abfb2d7 --- /dev/null +++ b/containers/core/packetizers.c @@ -0,0 +1,233 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/packetizers.h" +#include "containers/core/packetizers_private.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_utils.h" + +/** List of registered packetizers. */ +static VC_PACKETIZER_REGISTRY_ENTRY_T *registry; + +/*****************************************************************************/ +void vc_packetizer_register(VC_PACKETIZER_REGISTRY_ENTRY_T *entry) +{ + LOG_DEBUG(0, "registering packetizer %s", entry->name); + entry->next = registry; + registry = entry; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T vc_packetizer_load(VC_PACKETIZER_T *p_ctx) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + VC_PACKETIZER_REGISTRY_ENTRY_T *entry; + + /* Try all the packetizers until we find the right one */ + for (entry = registry; entry; entry = entry->next) + { + status = entry->open(p_ctx); + if(status != VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED) + break; + } + + return status; +} + +/*****************************************************************************/ +static void vc_packetizer_unload(VC_PACKETIZER_T *p_ctx) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); +} + +/*****************************************************************************/ +VC_PACKETIZER_T *vc_packetizer_open( VC_CONTAINER_ES_FORMAT_T *in, + VC_CONTAINER_FOURCC_T out_variant, VC_CONTAINER_STATUS_T *p_status ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_PACKETIZER_T *p_ctx = 0; + + /* Allocate our context before trying out the different packetizers */ + p_ctx = malloc( sizeof(*p_ctx) + sizeof(*p_ctx->priv)); + if(!p_ctx) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(p_ctx, 0, sizeof(*p_ctx) + sizeof(*p_ctx->priv)); + p_ctx->priv = (VC_PACKETIZER_PRIVATE_T *)(p_ctx + 1); + bytestream_init( &p_ctx->priv->stream ); + + p_ctx->in = vc_container_format_create(in->extradata_size); + if(!p_ctx->in) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + p_ctx->out = vc_container_format_create(in->extradata_size); + if(!p_ctx->out) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + + vc_container_format_copy( p_ctx->in, in, in->extradata_size ); + p_ctx->in->extradata_size = 0; + vc_container_format_copy( p_ctx->out, p_ctx->in, in->extradata_size ); + p_ctx->in->extradata_size = in->extradata_size; + p_ctx->out->extradata = p_ctx->in->extradata; + p_ctx->out->extradata_size = p_ctx->in->extradata_size; + p_ctx->out->codec_variant = out_variant; + + vc_container_time_init(&p_ctx->priv->time, 1000000); + + status = vc_packetizer_load(p_ctx); + if(status != VC_CONTAINER_SUCCESS) + goto error; + + end: + if(p_status) *p_status = status; + return p_ctx; + + error: + if(p_ctx) vc_packetizer_close(p_ctx); + p_ctx = NULL; + goto end; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_packetizer_close( VC_PACKETIZER_T *p_ctx ) +{ + VC_CONTAINER_BYTESTREAM_T *stream; + VC_CONTAINER_PACKET_T *packet, *next; + + if(!p_ctx) return VC_CONTAINER_SUCCESS; + + stream = &p_ctx->priv->stream; + + if(p_ctx->in) vc_container_format_delete(p_ctx->in); + if(p_ctx->out) vc_container_format_delete(p_ctx->out); + if(p_ctx->priv->pf_close) p_ctx->priv->pf_close(p_ctx); + if(p_ctx->priv->module_handle) vc_packetizer_unload(p_ctx); + + /* Free the bytestream */ + for(packet = stream->first; packet; packet = next) + { + next = packet->next; + if(packet->framework_data) free(packet); + } + + free(p_ctx); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_packetizer_push( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_PACKET_T *in) +{ + /* Do some sanity checking on packet ? */ + + in->framework_data = 0; + bytestream_push(&p_ctx->priv->stream, in); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_packetizer_pop( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_PACKET_T **in, VC_PACKETIZER_FLAGS_T flags) +{ + VC_CONTAINER_BYTESTREAM_T *stream = &p_ctx->priv->stream; + VC_CONTAINER_PACKET_T *packet, *new, **prev; + + /* Release the packets which have been read */ + while((*in = bytestream_pop(stream)) != NULL) + { + if(*in && (*in)->framework_data) + { + free(*in); + continue; + } + + if(*in) + return VC_CONTAINER_SUCCESS; + } + + if(!(flags & VC_PACKETIZER_FLAG_FORCE_RELEASE_INPUT)) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + + /* Look for the 1st non-framework packet */ + for (packet = stream->first, prev = &stream->first; + packet && packet->framework_data; prev = &packet->next, packet = packet->next); + + if (!packet || (packet && packet->framework_data)) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + + /* We'll currently alloc an internal packet for each packet the client forcefully releases. + * We could probably do something a bit more clever than that though. */ + /* Replace the packet with a newly allocated one */ + new = malloc(sizeof(*packet) + packet->size); + if(!new) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + *new = *packet; + new->framework_data = new; + if(!new->next) + stream->last = &new->next; + if(stream->current == packet) + stream->current = new; + *prev = new; + new->data = (uint8_t *)&new[1]; + memcpy(new->data, packet->data, packet->size); + *in = packet; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_packetizer_read( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet, VC_PACKETIZER_FLAGS_T flags) +{ + if(!packet && !(flags & VC_CONTAINER_READ_FLAG_SKIP)) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + if(!packet && (flags & VC_CONTAINER_READ_FLAG_INFO)) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + if(packet && !packet->data && + (!(flags & VC_CONTAINER_READ_FLAG_INFO) && + !(flags & VC_CONTAINER_READ_FLAG_SKIP))) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + /* Always having a packet structure to work with simplifies things */ + if(!packet) + packet = &p_ctx->priv->packet; + + return p_ctx->priv->pf_packetize(p_ctx, packet, flags); +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_packetizer_reset( VC_PACKETIZER_T *p_ctx ) +{ + VC_CONTAINER_BYTESTREAM_T *stream = &p_ctx->priv->stream; + + bytestream_skip( stream, stream->bytes - stream->current_offset - stream->offset ); + + if (p_ctx->priv->pf_reset) + return p_ctx->priv->pf_reset(p_ctx); + else + return VC_CONTAINER_SUCCESS; +} diff --git a/containers/core/packetizers_private.h b/containers/core/packetizers_private.h new file mode 100644 index 0000000..ad5f8f5 --- /dev/null +++ b/containers/core/packetizers_private.h @@ -0,0 +1,111 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_PACKETIZERS_PRIVATE_H +#define VC_PACKETIZERS_PRIVATE_H + +/** \file + * Private interface for packetizers + */ + +#include +#include "containers/packetizers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_bytestream.h" +#include "containers/core/containers_time.h" + +/** \defgroup VcPacketizerModuleApi Packetizer Module API + * Private interface for modules implementing packetizers */ +/* @{ */ + +/** Context private to the packetizer instance. This private context is used to + * store data which shouldn't be exported by the public API. */ +typedef struct VC_PACKETIZER_PRIVATE_T +{ + /** Pointer to the private data of the packetizer module in use */ + struct VC_PACKETIZER_MODULE_T *module; + + /** Bytestream abstraction layer */ + struct VC_CONTAINER_BYTESTREAM_T stream; + + /** Current stream time */ + VC_CONTAINER_TIME_T time; + + /** Packetize the bytestream. + * + * \param context Pointer to the context of the instance of the packetizer + * \param out Pointer to the output packet structure which needs to be filled + * \param flags Miscellaneous flags controlling the packetizing + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_packetize)( VC_PACKETIZER_T *context, + VC_CONTAINER_PACKET_T *out, VC_PACKETIZER_FLAGS_T flags ); + + /** Reset packetizer state. + * + * \param context Pointer to the context of the instance of the packetizer + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_reset)( VC_PACKETIZER_T *context ); + + /** Closes a packetizer module. + * + * \param context Pointer to the context of the instance to close + * \return the status of the operation + */ + VC_CONTAINER_STATUS_T (*pf_close)( struct VC_PACKETIZER_T *context ); + + /** Pointer to the packetizer module code and symbols*/ + void *module_handle; + + /** Temporary packet structure used when the caller does not provide one */ + VC_CONTAINER_PACKET_T packet; + +} VC_PACKETIZER_PRIVATE_T; + +/** Structure used by packetizers to register themselves with the core. */ +typedef struct VC_PACKETIZER_REGISTRY_ENTRY_T +{ + struct VC_PACKETIZER_REGISTRY_ENTRY_T *next; /**< To link entries together */ + const char *name; /**< Name of the packetizer */ + VC_CONTAINER_STATUS_T (*open)( VC_PACKETIZER_T * ); /**< Called to open packetizer */ +} VC_PACKETIZER_REGISTRY_ENTRY_T; + +/** Register a packetizer with the core. + * + * \param entry Entry to register with the core + */ +void vc_packetizer_register(VC_PACKETIZER_REGISTRY_ENTRY_T *entry); + +/** Utility macro used to register a packetizer with the core */ +#define VC_PACKETIZER_REGISTER(func, name) \ +VC_CONTAINER_CONSTRUCTOR(func##_register); \ +static VC_PACKETIZER_REGISTRY_ENTRY_T registry_entry = {0, name, func}; \ +void func##_register(void) { vc_packetizer_register(®istry_entry); } + +/* @} */ + +#endif /* VC_PACKETIZERS_PRIVATE_H */ diff --git a/containers/dummy/CMakeLists.txt b/containers/dummy/CMakeLists.txt new file mode 100644 index 0000000..4643438 --- /dev/null +++ b/containers/dummy/CMakeLists.txt @@ -0,0 +1,12 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(writer_dummy ${LIBRARY_TYPE} dummy_writer.c) + +target_link_libraries(writer_dummy containers) + +install(TARGETS writer_dummy DESTINATION ${VMCS_PLUGIN_DIR}) diff --git a/containers/dummy/dummy_writer.c b/containers/dummy/dummy_writer.c new file mode 100644 index 0000000..0430b98 --- /dev/null +++ b/containers/dummy/dummy_writer.c @@ -0,0 +1,137 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track[2]; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T dummy_writer_open( VC_CONTAINER_T * ); + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T dummy_writer_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + for(; p_ctx->tracks_num > 0; p_ctx->tracks_num--) + vc_container_free_track(p_ctx, p_ctx->tracks[p_ctx->tracks_num-1]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T dummy_writer_write( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(packet); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T dummy_writer_control( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_CONTROL_T operation, va_list args ) +{ + VC_CONTAINER_TRACK_T *track; + VC_CONTAINER_PARAM_UNUSED(args); + + switch(operation) + { + case VC_CONTAINER_CONTROL_TRACK_ADD: + if(p_ctx->tracks_num >= 2) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + /* Allocate and initialise track data */ + p_ctx->tracks[p_ctx->tracks_num] = track = vc_container_allocate_track(p_ctx, 0); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + p_ctx->tracks_num++; + return VC_CONTAINER_SUCCESS; + + case VC_CONTAINER_CONTROL_TRACK_ADD_DONE: + return VC_CONTAINER_SUCCESS; + + default: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + } +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T dummy_writer_open( VC_CONTAINER_T *p_ctx ) +{ + const char *extension = vc_uri_path_extension(p_ctx->priv->uri); + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + + /* Check if the user has specified a container */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + /* Check we're the right writer for this */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(strcasecmp(extension, "dummy")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = module->track; + + p_ctx->capabilities |= VC_CONTAINER_CAPS_DYNAMIC_TRACK_ADD; + + p_ctx->priv->pf_close = dummy_writer_close; + p_ctx->priv->pf_write = dummy_writer_write; + p_ctx->priv->pf_control = dummy_writer_control; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "dummy: error opening stream (%i)", status); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak writer_open dummy_writer_open +#endif diff --git a/containers/flash/CMakeLists.txt b/containers/flash/CMakeLists.txt new file mode 100644 index 0000000..bf53935 --- /dev/null +++ b/containers/flash/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_flv ${LIBRARY_TYPE} flv_reader.c) + +target_link_libraries(reader_flv containers) + +install(TARGETS reader_flv DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/flash/flv_reader.c b/containers/flash/flv_reader.c new file mode 100644 index 0000000..6e15e6b --- /dev/null +++ b/containers/flash/flv_reader.c @@ -0,0 +1,1174 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +/* Work-around for MSVC debugger issue */ +#define VC_CONTAINER_MODULE_T VC_CONTAINER_MODULE_FLV_READER_T +#define VC_CONTAINER_TRACK_MODULE_T VC_CONTAINER_TRACK_MODULE_FLV_READER_T + +//#define ENABLE_FLV_EXTRA_LOGGING +#define CONTAINER_IS_BIG_ENDIAN +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_index.h" +#include "containers/core/containers_logging.h" +#undef CONTAINER_HELPER_LOG_INDENT +#define CONTAINER_HELPER_LOG_INDENT(a) 0 + +VC_CONTAINER_STATUS_T flv_reader_open( VC_CONTAINER_T *p_ctx ); + +/****************************************************************************** +Defines. +******************************************************************************/ +#define FLV_TRACKS_MAX 2 + +#define FLV_TAG_TYPE_AUDIO 8 +#define FLV_TAG_TYPE_VIDEO 9 +#define FLV_TAG_TYPE_METADATA 18 +#define FLV_TAG_HEADER_SIZE 15 + +#define FLV_SCRIPT_DATA_TYPE_NUMBER 0 +#define FLV_SCRIPT_DATA_TYPE_BOOL 1 +#define FLV_SCRIPT_DATA_TYPE_STRING 2 +#define FLV_SCRIPT_DATA_TYPE_ECMA 8 +#define FLV_SCRIPT_DATA_TYPE_LONGSTRING 12 + +#define FLV_FLAG_DISCARD 1 +#define FLV_FLAG_KEYFRAME 2 +#define FLV_FLAG_INTERFRAME 4 + +/****************************************************************************** +Type definitions. +******************************************************************************/ +typedef struct +{ + VC_CONTAINER_STATUS_T status; + + int64_t tag_position; /* position of the current tag we're reading */ + int64_t data_position; /* position to the start of the data within the tag */ + int data_offset; /* current position inside the tag's data */ + int data_size; /* size of the data from the current tag */ + int tag_prev_size; /* size of the previous tag in the stream */ + int flags; /* flags for the current tag */ + uint32_t timestamp; /* timestamp for the current tag */ + uint32_t track; /* track the current tag belongs to */ + VC_CONTAINER_INDEX_T *index; /* index of key frames */ + +} FLV_READER_STATE_T; + +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + FLV_READER_STATE_T *state; + FLV_READER_STATE_T track_state; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *tracks[FLV_TRACKS_MAX]; + + int64_t data_offset; /*< offset to the first FLV tag in the stream */ + + FLV_READER_STATE_T state; /*< global state of the reader */ + + int audio_track; + int video_track; + + uint32_t meta_videodatarate; + uint32_t meta_audiodatarate; + float meta_framerate; + uint32_t meta_width; + uint32_t meta_height; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Static functions within this file. +******************************************************************************/ +/** Reads an FLV tag header + * + * @param p_ctx pointer to our context + * @param[out] p_prev_size size of the previous tag + * @param[out] p_type type of the tag + * @param[out] p_type size of the tag + * @param[out] p_type timestamp for the tag + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_read_tag_header(VC_CONTAINER_T *p_ctx, int *p_prev_size, + int *p_type, int *p_size, uint32_t *p_timestamp) +{ + int prev_size, type, size; + uint32_t timestamp; + + prev_size = READ_U32(p_ctx, "PreviousTagSize"); + type = READ_U8(p_ctx, "TagType"); + size = READ_U24(p_ctx, "DataSize"); + timestamp = READ_U24(p_ctx, "Timestamp"); + timestamp |= (READ_U8(p_ctx, "TimestampExtended") << 24); + SKIP_U24(p_ctx, "StreamID"); + + if(p_prev_size) *p_prev_size = prev_size + 4; + if(p_type) *p_type = type; + if(p_size) *p_size = size; + if(p_timestamp) *p_timestamp = timestamp; + + return STREAM_STATUS(p_ctx); +} + +/** Reads an FLV video data header. + * This contains the codec id and the current frame type. + * + * @param p_ctx pointer to our context + * @param[out] codec video codec + * @param[out] frame_type type of the current frame + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_read_videodata_header(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_FOURCC_T *codec, int *frame_type) +{ + uint8_t header = READ_U8(p_ctx, "FrameType/CodecID"); + + if(frame_type) + *frame_type = (header >> 4) == 1 ? FLV_FLAG_KEYFRAME : + (header >> 4) == 3 ? FLV_FLAG_INTERFRAME : 0; + + switch(header &0xF) + { + case 2: *codec = VC_CONTAINER_CODEC_SPARK; break; + case 3: *codec = VC_FOURCC('s','c','r','1'); break; /* screen video */ + case 4: *codec = VC_CONTAINER_CODEC_VP6; break; + case 5: *codec = VC_FOURCC('v','p','6','a'); break; /* vp6 alpha */ + case 6: *codec = VC_FOURCC('s','c','r','2'); break; /* screen video 2 */ + case 7: *codec = VC_CONTAINER_CODEC_H264; break; + default: *codec = 0; break; + } + + return STREAM_STATUS(p_ctx); +} + +/** Get the properties of a video frame + * This is only really useful at setup time when trying to detect + * the type of content we are dealing with. + * This will try to get some of the properties of the video stream + * as well as codec configuration data if there is any. + * + * @param p_ctx pointer to our context + * @param track track number this data/tag belongs to + * @param size size of the data we are parsing + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_read_videodata_properties(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, int size) +{ + VC_CONTAINER_STATUS_T status; + int width = 0, height = 0; + + if(track->format->codec == VC_CONTAINER_CODEC_VP6 || + track->format->codec == VC_FOURCC('v','p','6','a')) + { + /* Just extract the width / height */ + uint8_t data = _READ_U8(p_ctx); + _SKIP_U16(p_ctx); + height = _READ_U8(p_ctx) * 16; + width = _READ_U8(p_ctx) * 16; + width -= data >> 4; + height -= data & 0xf; + } + else if(track->format->codec == VC_CONTAINER_CODEC_H264) + { + uint8_t type = _READ_U8(p_ctx); size--; + if(type || size <= 8) return VC_CONTAINER_ERROR_CORRUPTED; + _SKIP_U24(p_ctx); size-=3; + + track->format->codec_variant = VC_FOURCC('a','v','c','C'); + status = vc_container_track_allocate_extradata(p_ctx, track, size); + if(status != VC_CONTAINER_SUCCESS) return status; + track->format->extradata_size = READ_BYTES(p_ctx, track->format->extradata, size); + } + + track->format->type->video.width = width; + track->format->type->video.height = height; + + return STREAM_STATUS(p_ctx); +} + +/** Reads an FLV audio data header. + * This contains the codec id, samplerate, number of channels and bitrate. + * + * @param p_ctx pointer to our context + * @param[out] codec audio codec + * @param[out] p_samplerate audio sampling rate + * @param[out] p_channels number of audio channels + * @param[out] p_bps bits per sample + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_read_audiodata_header(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_FOURCC_T *codec, int *p_samplerate, int *p_channels, int *p_bps) +{ + int samplerate, channels, bps; + uint8_t header = _READ_U8(p_ctx); + + switch((header >> 2) & 0x3) + { + case 0: samplerate = 5512; break; + case 1: samplerate = 11025; break; + case 2: samplerate = 22050; break; + default: + case 3: samplerate = 44100; break; + } + + channels = 1 << (header & 1); + bps = 8 << ((header >> 1) & 1); + + switch(header >> 4) + { + case 0: *codec = bps == 8 ? VC_CONTAINER_CODEC_PCM_UNSIGNED : VC_CONTAINER_CODEC_PCM_SIGNED; break; + case 1: *codec = VC_CONTAINER_CODEC_ADPCM_SWF; break; + case 2: *codec = VC_CONTAINER_CODEC_MPGA; break; + case 3: *codec = bps == 8 ? VC_CONTAINER_CODEC_PCM_UNSIGNED : VC_CONTAINER_CODEC_PCM_SIGNED_LE; break; + case 4: *codec = VC_CONTAINER_CODEC_NELLYMOSER; samplerate = 8000; channels = 1; break; + case 5: *codec = VC_CONTAINER_CODEC_NELLYMOSER; samplerate = 16000; channels = 1; break; + case 6: *codec = VC_CONTAINER_CODEC_NELLYMOSER; channels = 1; break; + case 7: *codec = VC_CONTAINER_CODEC_ALAW; break; + case 8: *codec = VC_CONTAINER_CODEC_MULAW; break; + case 10: *codec = VC_CONTAINER_CODEC_MP4A; samplerate = 44100; channels = 2; break; + case 11: *codec = VC_CONTAINER_CODEC_SPEEX; break; + case 14: *codec = VC_CONTAINER_CODEC_MPGA; samplerate = 8000; break; + default: *codec = 0; break; + } + + if(p_samplerate) *p_samplerate = samplerate; + if(p_channels) *p_channels = channels; + if(p_bps) *p_bps = bps; + + return STREAM_STATUS(p_ctx); +} + +/** Get the properties of an audio frame + * This is only really useful at setup time when trying to detect + * the type of content we are dealing with. + * This will try to get some of the properties of the audio stream + * as well as codec configuration data if there is any. + * + * @param p_ctx pointer to our context + * @param track track number this data/tag belongs to + * @param size size of the data we are parsing + * @param samplerate sampling rate of the audio data + * @param channels number of channels of the audio data + * @param bps bits per sample + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_read_audiodata_properties(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, int size, int samplerate, int channels, int bps) +{ + static const int aac_freq[16] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, + 22050, 16000, 12000, 11025, 8000, 7350}; + VC_CONTAINER_STATUS_T status; + + if(track->format->codec == VC_CONTAINER_CODEC_MP4A) + { + uint8_t *p_data, data = _READ_U8(p_ctx); + size--; + if(data || size <= 0) return VC_CONTAINER_ERROR_FAILED; + + /* Read the AudioSpecificConfig */ + status = vc_container_track_allocate_extradata(p_ctx, track, size); + if(status != VC_CONTAINER_SUCCESS) return status; + track->format->extradata_size = READ_BYTES(p_ctx, track->format->extradata, size); + + if(track->format->extradata_size >= 2) + { + p_data = track->format->extradata; + data = ((p_data[0] & 0x7) << 1) | (p_data[1] >> 7); + if(data >= countof(aac_freq)) + return VC_CONTAINER_ERROR_FAILED; + + samplerate = aac_freq[data]; + channels = (p_data[1] >> 3) & 0xf; + if(track->format->extradata_size >= 5 && data == 0xf) + { + samplerate = ((p_data[1] & 0x7f) << 17) | (p_data[2] << 9) | + (p_data[3] << 1) | (p_data[4] >> 7); + channels = (p_data[4] >> 3) & 0xf; + } + } + } + + track->format->type->audio.sample_rate = samplerate; + track->format->type->audio.channels = channels; + track->format->type->audio.bits_per_sample = bps; + + return STREAM_STATUS(p_ctx); +} + +/** Reads an FLV metadata tag. + * This contains metadata information about the stream. + * All the data we extract from this will be placed directly in the context. + * + * @param p_ctx pointer to our context + * @param size size of the tag + * @return FLV_FILE_NO_ERROR on success + */ +static int flv_read_metadata(VC_CONTAINER_T *p_ctx, int size) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; +#define MAX_METADATA_STRING_SIZE 25 + char psz_string[MAX_METADATA_STRING_SIZE+1]; + uint16_t length, num_values; + double f_value; + uint64_t u_value; + uint8_t type; + + /* We're looking for an onMetaData script */ + type = READ_U8(p_ctx, "Type"); size--; + if(type != FLV_SCRIPT_DATA_TYPE_STRING) return VC_CONTAINER_SUCCESS; + length = READ_U16(p_ctx, "StringLength"); size -= 2; + if(!length || length > size || length > MAX_METADATA_STRING_SIZE) return VC_CONTAINER_SUCCESS; + if(READ_BYTES(p_ctx, psz_string, length) != length) return VC_CONTAINER_SUCCESS; + psz_string[length] = 0; size -= length; + if(strcmp(psz_string, "onMetaData")) return VC_CONTAINER_SUCCESS; + if(size < 5) return VC_CONTAINER_SUCCESS; + type = READ_U8(p_ctx, "Type"); size--; + if(type != FLV_SCRIPT_DATA_TYPE_ECMA) return VC_CONTAINER_SUCCESS; + num_values = READ_U32(p_ctx, "ECMAArrayLength"); size -= 4; + + /* We found our script, now extract the metadata values */ + while(num_values-- && size >= 2) + { + uint16_t length = _READ_U16(p_ctx); size -= 2; + if(!length || length >= size || length > MAX_METADATA_STRING_SIZE) break; + if(READ_BYTES(p_ctx, psz_string, length) != length) break; + psz_string[length] = 0; size -= length; + type = _READ_U8(p_ctx); size--; + + switch(type) + { + case FLV_SCRIPT_DATA_TYPE_NUMBER: + /* We only cope with DOUBLE types*/ + if(size < 8) return VC_CONTAINER_SUCCESS; + + u_value = _READ_U64(p_ctx); size -= 8; + /* Convert value into a double */ + { + int64_t value = ((u_value & ((UINT64_C(1)<<52)-1)) + (UINT64_C(1)<<52)) * ((((int64_t)u_value)>>63)|1); + int exp = ((u_value>>52)&0x7FF)-1075 + 16; + if(exp >= 0) value <<= exp; + else value >>= -exp; + f_value = ((double)value) / (1 << 16); + } + + LOG_DEBUG(p_ctx, "metadata (%s=%i.%i)", psz_string, + ((int)(f_value*100))/100, ((int)(f_value*100))%100); + + if(!strcmp(psz_string, "duration")) + p_ctx->duration = (int64_t)(f_value*INT64_C(1000000)); + if(!strcmp(psz_string, "videodatarate")) + module->meta_videodatarate = (uint32_t)f_value; + if(!strcmp(psz_string, "width")) + module->meta_width = (uint32_t)f_value; + if(!strcmp(psz_string, "height")) + module->meta_height = (uint32_t)f_value; + if(!strcmp(psz_string, "framerate")) + module->meta_framerate = f_value; + if(!strcmp(psz_string, "audiodatarate")) + module->meta_audiodatarate = (uint32_t)f_value; + continue; + + /* We skip these */ + case FLV_SCRIPT_DATA_TYPE_BOOL: + if(size < 1) return VC_CONTAINER_SUCCESS; + u_value = _READ_U8(p_ctx); size -= 1; + LOG_DEBUG(p_ctx, "metadata (%s=%i)", psz_string, (int)u_value); + continue; + + case FLV_SCRIPT_DATA_TYPE_STRING: + if(size < 2) return VC_CONTAINER_SUCCESS; + length = _READ_U16(p_ctx); size -= 2; + if(length > size) return VC_CONTAINER_SUCCESS; + SKIP_BYTES(p_ctx, length); size -= length; + LOG_DEBUG(p_ctx, "metadata skipping (%s)", psz_string); + continue; + + /* We can't cope with anything else */ + default: + LOG_DEBUG(p_ctx, "unknown amf type (%s,%i)", psz_string, type); + return VC_CONTAINER_SUCCESS; + } + } + + return STREAM_STATUS(p_ctx); +} + +/** Reads an FLV frame header. + * This reads the current tag header and matches the contained frame + * with one of the tracks we have. If no match can be found, the frame is marked + * for discarding. The current read position will be updated to the start + * of the data (i.e. the frame) contained within the FLV tag. + * + * @param p_ctx pointer to our context + * @param[out] p_track track this frame belongs to + * @param[out] p_size size of the frame + * @param[out] p_timestamp timestamp of the frame + * @param[out] p_flags flags associated with the frame + * @param b_extra_check whether to perform extra sanity checking on the tag + * @return VC_CONTAINER_SUCCESS on success + */ +static int flv_read_frame_header(VC_CONTAINER_T *p_ctx, int *p_prev_size, + int *p_track, int *p_size, uint32_t *p_timestamp, int *p_flags, + int b_extra_check) +{ + int64_t position = STREAM_POSITION(p_ctx); + int type, size, flags = 0, frametype = 0; + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_ES_TYPE_T es_type = VC_CONTAINER_ES_TYPE_UNKNOWN; + unsigned int track = p_ctx->tracks_num; + uint32_t codec = 0; + + status = flv_read_tag_header(p_ctx, p_prev_size, &type, &size, p_timestamp); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) return status; + if(position == STREAM_POSITION(p_ctx)) return VC_CONTAINER_ERROR_EOS; + if(type == 0) return VC_CONTAINER_ERROR_CORRUPTED; + + /* Sanity checking */ + if(b_extra_check && type != FLV_TAG_TYPE_AUDIO && + type != FLV_TAG_TYPE_VIDEO && type != FLV_TAG_TYPE_METADATA) + return VC_CONTAINER_ERROR_CORRUPTED; + + /* We're only interested in audio / video */ + if((type != FLV_TAG_TYPE_AUDIO && type != FLV_TAG_TYPE_VIDEO) || !size) + { + flags |= FLV_FLAG_DISCARD; + goto end; + } + + if(type == FLV_TAG_TYPE_AUDIO) + { + flv_read_audiodata_header(p_ctx, &codec, 0, 0, 0); + es_type = VC_CONTAINER_ES_TYPE_AUDIO; + } + else if(type == FLV_TAG_TYPE_VIDEO) + { + flv_read_videodata_header(p_ctx, &codec, &frametype); + es_type = VC_CONTAINER_ES_TYPE_VIDEO; + } + size--; + + /* Find which track this belongs to */ + for(track = 0; track < p_ctx->tracks_num; track++) + if(es_type == p_ctx->tracks[track]->format->es_type) break; + if(track == p_ctx->tracks_num) + flags |= FLV_FLAG_DISCARD; + + /* Sanity checking */ + if(b_extra_check && codec != p_ctx->tracks[track]->format->codec) + return VC_CONTAINER_ERROR_CORRUPTED; + + end: + // add to the index if we have one, and we're not discarding this frame. + // also require that we either have no video track or this is a video frame marked as a key frame. + if(p_ctx->priv->module->state.index && !(flags & FLV_FLAG_DISCARD) && + (p_ctx->priv->module->video_track < 0 || (es_type == VC_CONTAINER_ES_TYPE_VIDEO && (frametype & FLV_FLAG_KEYFRAME)))) + vc_container_index_add(p_ctx->priv->module->state.index, (int64_t) (*p_timestamp) * 1000LL, position); + + *p_flags = flags | frametype; + *p_size = size; + *p_track = track; + return VC_CONTAINER_SUCCESS; +} + +/** Validate the data contained within the frame and update the read + * position to the start of the frame data that we want to feed to the codec. + * + * Each codec is packed slightly differently so this function is necessary + * to prepare for reading the actual codec data. + * + * @param p_ctx pointer to our context + * @param track track this frame belongs to + * @param[in] p_size size of the frame + * @param[out] p_size updated size of the frame + * @param[in] p_timestamp timestamp for the frame + * @param[out] p_timestamp updated timestamp for the frame + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_validate_frame_data(VC_CONTAINER_T *p_ctx, + int track, int *p_size, uint32_t *p_timestamp) +{ + int32_t time_offset; + + if(track >= (int)p_ctx->tracks_num) + return *p_size ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_CONTINUE; + + switch(p_ctx->tracks[track]->format->codec) + { + case VC_CONTAINER_CODEC_VP6: + if(*p_size < 1) return VC_CONTAINER_ERROR_CORRUPTED; + _READ_U8(p_ctx); *p_size -= 1; + break; + case VC_CONTAINER_CODEC_MP4A: + if(*p_size < 1) return VC_CONTAINER_ERROR_CORRUPTED; + *p_size -= 1; + if(_READ_U8(p_ctx)!=1) return VC_CONTAINER_ERROR_CONTINUE; /* empty frame*/ + break; + case VC_CONTAINER_CODEC_H264: + if(*p_size < 4) return VC_CONTAINER_ERROR_CORRUPTED; + *p_size -= 1; + if(_READ_U8(p_ctx)!=1) return VC_CONTAINER_ERROR_CONTINUE; /* empty frame*/ + time_offset = _READ_U24(p_ctx); + time_offset <<= 8; time_offset >>= 8; /* change to signed */ + *p_timestamp += time_offset; + *p_size -= 3; + break; + default: + break; + } + + return *p_size ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_CONTINUE; +} + +/** Small utility function to update the reading position of a track + */ +static void flv_update_track_position(VC_CONTAINER_T *p_ctx, int track, + int64_t tag_position, int tag_prev_size, int64_t data_position, + int data_size, uint32_t timestamp, int flags) +{ + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[track]->priv->module; + track_module->state->tag_position = tag_position; + track_module->state->tag_prev_size = tag_prev_size; + track_module->state->data_position = data_position; + track_module->state->data_size = data_size; + track_module->state->data_offset = 0; + track_module->state->timestamp = timestamp; + track_module->state->flags = flags; + track_module->state->track = track; +} + +/** Utility function to find the next frame of a given track in the stream. + * + * This will basically walk through all the tags in the file until it + * finds a tag/frame which belongs to the given track. + * + * @param p_ctx pointer to our context + * @param track track wanted + * @param[out] p_size size of the frame + * @param[out] p_timestamp timestamp of the frame + * @param[out] p_flags flags associated with the frame + * @param b_keyframe whether we specifically want a keyframe or not + * @param b_extra_check whether to perform extra sanity checking on the tag + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_find_next_frame(VC_CONTAINER_T *p_ctx, int track, int *p_size, + uint32_t *p_timestamp, int *p_flags, int b_keyframe, int b_extra_check) +{ + int frame_track, prev_size, size, flags; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + FLV_READER_STATE_T *state = p_ctx->tracks[track]->priv->module->state; + uint32_t timestamp; + int64_t position; + VC_CONTAINER_PARAM_UNUSED(b_extra_check); + + /* Seek to the next tag in the stream or the current position + * if none of its data has been consumed */ + position = state->tag_position; + if(state->data_offset) + position = state->data_position + state->data_size; + status = SEEK(p_ctx, position); + if(status != VC_CONTAINER_SUCCESS) return status; + + /* Look for the next frame we want */ + while (status == VC_CONTAINER_SUCCESS) + { + position = STREAM_POSITION(p_ctx); + status = flv_read_frame_header(p_ctx, &prev_size, &frame_track, + &size, ×tamp, &flags, 0); + if(status != VC_CONTAINER_SUCCESS) break; + + if(flags & FLV_FLAG_DISCARD) goto skip; + if(frame_track != track) goto skip; + + if(b_keyframe && p_ctx->tracks[track]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO && + !(flags & FLV_FLAG_KEYFRAME)) goto skip; + + if(flv_validate_frame_data(p_ctx, track, &size, ×tamp) != VC_CONTAINER_SUCCESS) + goto skip; + + /* We have what we need */ + flv_update_track_position(p_ctx, track, position, prev_size, STREAM_POSITION(p_ctx), + size, timestamp, flags); + break; + + skip: + flv_update_track_position(p_ctx, track, position, prev_size, STREAM_POSITION(p_ctx), + size, timestamp, 0); + state->data_offset = size; /* consume data */ + + if(SKIP_BYTES(p_ctx, size) != (size_t)size) status = STREAM_STATUS(p_ctx); + } + + if(!status) + { + if(p_size) *p_size = size; + if(p_timestamp) *p_timestamp = timestamp; + if(p_flags) *p_flags = flags; + } + + return status; +} + +/** Utility function to find the previous frame of a given track in the stream. + * + * This will basically walk back through all the tags in the file until it + * finds a tag/frame which belongs to the given track. + * + * @param p_ctx pointer to our context + * @param track track wanted + * @param[out] p_size size of the frame + * @param[out] p_timestamp timestamp of the frame + * @param[out] p_flags flags associated with the frame + * @param b_keyframe whether we specifically want a keyframe or not + * @param b_extra_check whether to perform extra sanity checking on the tag + * @return VC_CONTAINER_SUCCESS on success + */ +static VC_CONTAINER_STATUS_T flv_find_previous_frame(VC_CONTAINER_T *p_ctx, int track, int *p_size, + uint32_t *p_timestamp, int *p_flags, int b_keyframe, int b_extra_check) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + FLV_READER_STATE_T *state = p_ctx->tracks[track]->priv->module->state; + int frame_track, prev_size, size, flags; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t timestamp; + int64_t position; + + /* Look for the previous frame we want */ + while (status == VC_CONTAINER_SUCCESS) + { + /* Seek to the previous tag in the stream */ + position = state->tag_position - state->tag_prev_size; + if(position < module->data_offset) position = module->data_offset; + status = SEEK(p_ctx, position); + if(status != VC_CONTAINER_SUCCESS) return status; + + status = flv_read_frame_header(p_ctx, &prev_size, &frame_track, + &size, ×tamp, &flags, 0); + if(status) break; + + if(flags & FLV_FLAG_DISCARD) goto skip; + if(frame_track != track) goto skip; + + if(b_keyframe && p_ctx->tracks[track]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO && + !(flags & FLV_FLAG_KEYFRAME)) goto skip; + + if(flv_validate_frame_data(p_ctx, track, &size, ×tamp) != VC_CONTAINER_SUCCESS) + goto skip; + + /* We have what we need */ + flv_update_track_position(p_ctx, track, position, prev_size, STREAM_POSITION(p_ctx), + size, timestamp, flags); + break; + + skip: + if(position <= module->data_offset) + { + /* We're back at the beginning but we still want to return something */ + flv_update_track_position(p_ctx, track, (int64_t)module->data_offset, 0, + (int64_t)module->data_offset, 0, 0, 0); + return flv_find_next_frame(p_ctx, track, p_size, p_timestamp, p_flags, b_keyframe, b_extra_check); + } + flv_update_track_position(p_ctx, track, position, prev_size, STREAM_POSITION(p_ctx), + size, timestamp, 0); + state->data_offset = size; /* consume data */ + } + + if(!status) + { + if(p_size) *p_size = size; + if(p_timestamp) *p_timestamp = timestamp; + if(p_flags) *p_flags = flags; + } + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T flv_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + + for(i = 0; i < p_ctx->tracks_num; i++) + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + + if(module->state.index) + vc_container_index_free(module->state.index); + + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T flv_read_sample_header( VC_CONTAINER_T *p_ctx, + FLV_READER_STATE_T *state) +{ + int track, prev_size, size, flags; + uint32_t timestamp; + int64_t position; + + /* Check if we still have some data left to read from the current frame */ + if(state->data_offset < state->data_size) + return state->status; + + /* Read the next tag header */ + position = STREAM_POSITION(p_ctx); + state->status = flv_read_frame_header(p_ctx, &prev_size, &track, + &size, ×tamp, &flags, 0); + if(state->status != VC_CONTAINER_SUCCESS) + return state->status; + + state->status = flv_validate_frame_data(p_ctx, track, &size, ×tamp); + if(state->status == VC_CONTAINER_ERROR_CONTINUE) + { + /* Skip it */ + state->status = VC_CONTAINER_SUCCESS; + track = p_ctx->tracks_num; + } + if(state->status != VC_CONTAINER_SUCCESS) + return state->status; + + state->tag_position = position; + state->data_position = STREAM_POSITION(p_ctx); + state->data_size = size; + state->data_offset = 0; + state->flags = flags; + state->tag_prev_size = prev_size; + state->timestamp = timestamp; + state->track = track; + return state->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T flv_read_sample_data( VC_CONTAINER_T *p_ctx, + FLV_READER_STATE_T *state, uint8_t *data, unsigned int *data_size ) +{ + unsigned int size = state->data_size - state->data_offset; + + if(state->status != VC_CONTAINER_SUCCESS) return state->status; + + if(data_size && *data_size < size) size = *data_size; + + if(!data) size = SKIP_BYTES(p_ctx, size); + else size = READ_BYTES(p_ctx, data, size); + state->data_offset += size; + + if(data_size) *data_size = size; + state->status = STREAM_STATUS(p_ctx); + + return state->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T flv_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + FLV_READER_STATE_T *state = &module->state; + unsigned int data_size; + + /* TODO: select right state */ + + status = flv_read_sample_header(p_ctx, state); + if(status != VC_CONTAINER_SUCCESS) return status; + +#ifdef ENABLE_FLV_EXTRA_LOGGING + LOG_DEBUG(p_ctx, "read_sample_header (%i,%i,%i/%i/%i/%i)", state->timestamp, state->flags, + (int)state->tag_position, (int)(state->data_position-state->tag_position), state->data_offset, state->data_size); +#endif + + if(state->track >= p_ctx->tracks_num || !p_ctx->tracks[state->track]->is_enabled) + { + /* Skip packet */ + status = flv_read_sample_data(p_ctx, state, 0, 0); + if(status != VC_CONTAINER_SUCCESS) return status; + return VC_CONTAINER_ERROR_CONTINUE; + } + + if((flags & VC_CONTAINER_READ_FLAG_SKIP) && !(flags & VC_CONTAINER_READ_FLAG_INFO)) /* Skip packet */ + return flv_read_sample_data(p_ctx, state, 0, 0); + + packet->dts = packet->pts = state->timestamp * (int64_t)1000; + packet->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END; + if(state->flags & FLV_FLAG_KEYFRAME) packet->flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME; + if(!state->data_offset) packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + packet->track = state->track; + + // The frame size is all the data + packet->frame_size = state->data_size; + + // the size is what's left + packet->size = state->data_size - state->data_offset; + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + return flv_read_sample_data(p_ctx, state, 0, 0); + else if(flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + data_size = packet->buffer_size; + status = flv_read_sample_data(p_ctx, state, packet->data, &data_size); + if(status != VC_CONTAINER_SUCCESS) + { + /* FIXME */ + return status; + } + + packet->size = data_size; + if(state->data_offset != state->data_size) + packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T flv_reader_seek(VC_CONTAINER_T *p_ctx, + int64_t *offset, VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + FLV_READER_STATE_T last_state = {0}; + FLV_READER_STATE_T *state; + uint32_t time = (*offset / 1000), timestamp, previous_time; + unsigned int i, track; + int size, past = 0; + int64_t position; + VC_CONTAINER_PARAM_UNUSED(mode); + + /* If we have a video track, then we want to find the keyframe closest to + * the requested time, otherwise we just look for the tag with the + * closest timestamp */ + + /* Select the track on which we'll do our seeking */ + for(i = 0, track = 0; i < p_ctx->tracks_num; i++) + { + if(p_ctx->tracks[i]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) continue; + track = i; + break; + } + if(track >= p_ctx->tracks_num) return VC_CONTAINER_ERROR_CORRUPTED; + state = p_ctx->tracks[track]->priv->module->state; + previous_time = state->timestamp; + + LOG_DEBUG(p_ctx, "seek (%i, prev %i)", time, previous_time); + + if(state->index && vc_container_index_get(state->index, flags & VC_CONTAINER_SEEK_FLAG_FORWARD, + offset, &position, &past) == VC_CONTAINER_SUCCESS) + { + flv_update_track_position(p_ctx, track, position, 0, position, 0, (uint32_t) (*offset / 1000LL), 0); + } + else + { + if(time < state->timestamp / 2) + flv_update_track_position(p_ctx, track, (int64_t)module->data_offset, 0, + (int64_t)module->data_offset, 0, 0, 0); + past = 1; + } + + /* If past it clear then we're done, otherwise we need to find our point from here */ + if(past == 0) + { + status = flv_find_next_frame(p_ctx, track, &size, ×tamp, 0, 1 /*keyframe*/, 0); + } + else + { + if(time > previous_time) + { + while(!status) + { + status = flv_find_next_frame(p_ctx, track, &size, ×tamp, 0, 1 /*keyframe*/, 0); + if(status) break; + + /* Check if we have our frame */ + if(time <= timestamp) break; + + last_state = *state; + state->data_offset = size; /* consume data */ + } + } + else + { + while(!status) + { + status = flv_find_previous_frame(p_ctx, track, &size, ×tamp, 0, 1 /*keyframe*/, 0); + if(status) break; + + /* Check if we have our frame */ + if(time >= timestamp) break; + + /* Detect when we've reached the 1st keyframe to avoid an infinite loop */ + if(state->timestamp == last_state.timestamp) break; + + last_state = *state; + state->data_offset = size; /* consume data */ + } + } + } + + if(status != VC_CONTAINER_SUCCESS && (flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + { + LOG_DEBUG(p_ctx, "seek failed (%i)", status); + return status; + } + else if(status != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "seek failed (%i), look for previous frame", status); + if(last_state.tag_position) *state = last_state; + else status = flv_find_previous_frame(p_ctx, track, &size, ×tamp, 0, 1 /*keyframe*/, 0); + } + + LOG_DEBUG(p_ctx, "seek done (%i)", timestamp); + state->status = VC_CONTAINER_SUCCESS; + last_state.status = VC_CONTAINER_SUCCESS; + + if(past == 1) + { + /* Make adjustment based on seek mode */ + if((flags & VC_CONTAINER_SEEK_FLAG_FORWARD) && timestamp < time && timestamp < previous_time) + { + if(last_state.tag_position) *state = last_state; + else status = flv_find_next_frame(p_ctx, track, &size, ×tamp, 0, 1 /*keyframe*/, 0); + } + else if(!(flags & VC_CONTAINER_SEEK_FLAG_FORWARD) && timestamp > time) + { + if(last_state.tag_position) *state = last_state; + else status = flv_find_previous_frame(p_ctx, track, &size, ×tamp, 0, 1 /*keyframe*/, 0); + } + + LOG_DEBUG(p_ctx, "seek adjustment (%i)", timestamp); + } + + if(state->data_position == last_state.data_position) + status = SEEK(p_ctx, state->data_position); + + *offset = timestamp * INT64_C(1000); + + return VC_CONTAINER_SUCCESS; +} + +/****************************************************************************** +Global function definitions. +******************************************************************************/ + +VC_CONTAINER_STATUS_T flv_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = 0; + uint8_t buffer[4], type_flags; + unsigned int i, frames, audio_present, video_present; + uint32_t data_offset; + + /* Check the FLV marker */ + if( PEEK_BYTES(p_ctx, buffer, 4) < 4 ) goto error; + if( buffer[0] != 'F' || buffer[1] != 'L' || buffer[2] != 'V' ) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + /* Check FLV version */ + if( buffer[3] > 4 ) + { + LOG_DEBUG(p_ctx, "Version too high: %d", buffer[3]); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + SKIP_BYTES(p_ctx, 4); /* FLV marker and version */ + + /* Find out which tracks should be available. + * FLV can only have up to 1 audio track and 1 video track. */ + type_flags = READ_U8(p_ctx, "TypeFlags"); + audio_present = !!(type_flags & 0x04); + video_present = !!(type_flags & 0x01); + + /* Sanity check DataOffset */ + data_offset = READ_U32(p_ctx, "DataOffset"); + if(data_offset < 9) goto error; + + /* + * We are dealing with an FLV file + */ + + LOG_DEBUG(p_ctx, "using flv reader"); + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = module->tracks; + module->data_offset = data_offset; + module->audio_track = -1; + module->video_track = -1; + + /* Skip to the start of the actual data */ + SKIP_BYTES(p_ctx, data_offset - 9); + + /* We'll start parsing a few of the FLV tags to find out the + * metadata / audio / video properties. + * The first tag we should see is the metadata one which will give us all the + * properties of the stream. However we do not rely on that being there and we + * actually look at the first audio / video tags as well. */ + for(frames = 0; frames < 20; frames++) + { + VC_CONTAINER_TRACK_T *track; + int64_t offset, skip; + int prev_size, type, size, channels, samplerate, bps; + uint32_t codec, timestamp; + + /* Stop there if we have everything we want */ + if(audio_present == (module->audio_track >= 0) && + video_present == (module->video_track >= 0)) break; + if(module->audio_track >= 0 && module->video_track >= 0) break; + + /* Start reading the next tag */ + if(flv_read_tag_header(p_ctx, &prev_size, &type, &size, ×tamp)) break; + if(!size) continue; + + offset = STREAM_POSITION(p_ctx); /* to keep track of how much data we read */ + + switch(type) + { + case FLV_TAG_TYPE_AUDIO: + if(module->audio_track >= 0) break; /* We already have our audio track */ + flv_read_audiodata_header(p_ctx, &codec, &samplerate, &channels, &bps); + + p_ctx->tracks[p_ctx->tracks_num] = track = + vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + track->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + track->format->codec = codec; + flv_read_audiodata_properties(p_ctx, track, size - 1, samplerate, channels, bps); + + module->audio_track = p_ctx->tracks_num++; + track->is_enabled = 1; + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + break; + + case FLV_TAG_TYPE_VIDEO: + if(module->video_track >= 0) break; /* We already have our video track */ + flv_read_videodata_header(p_ctx, &codec, 0); + + p_ctx->tracks[p_ctx->tracks_num] = track = + vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + track->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + track->format->codec = codec; + + status = flv_read_videodata_properties(p_ctx, track, size - 1); + if(status != VC_CONTAINER_SUCCESS) { vc_container_free_track(p_ctx, track); break; } + + module->video_track = p_ctx->tracks_num++; + track->is_enabled = 1; + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + break; + + case FLV_TAG_TYPE_METADATA: + flv_read_metadata(p_ctx, size); + break; + + default: break; + } + + /* Skip any data that's left unparsed from the current tag */ + skip = size - (STREAM_POSITION(p_ctx) - offset); + if(skip < 0) break; + SKIP_BYTES(p_ctx, (size_t)skip); + } + + /* Make sure we found something we can play */ + if(!p_ctx->tracks_num) {LOG_DEBUG(p_ctx, "didn't find any track"); goto error;} + + /* Try and create an index. All times are signed, so adding a base timestamp + * of zero means that we will always seek back to the start of the file, even if + * the actual frame timestamps start at some higher number. */ + if(vc_container_index_create(&module->state.index, 512) == VC_CONTAINER_SUCCESS) + vc_container_index_add(module->state.index, 0LL, (int64_t) data_offset); + + /* Use the metadata we read */ + if(module->audio_track >= 0) + { + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->audio_track]; + track->format->bitrate = module->meta_audiodatarate; + } + if(module->video_track >= 0) + { + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->video_track]; + track->format->bitrate = module->meta_videodatarate; + if(module->meta_framerate) + { + track->format->type->video.frame_rate_num = (uint32_t)(100 * module->meta_framerate); + track->format->type->video.frame_rate_den = 100; + } + + if(module->meta_width && module->meta_width > track->format->type->video.width) + track->format->type->video.width = module->meta_width; + if(module->meta_height && module->meta_height > track->format->type->video.height) + track->format->type->video.height = module->meta_height; + } + + status = SEEK(p_ctx, data_offset); + if(status != VC_CONTAINER_SUCCESS) goto error; + + /* Some initialisation */ + module->state.tag_position = data_offset; + module->state.data_position = data_offset; + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[i]->priv->module; + track_module->state = &module->state; + } + + if(STREAM_SEEKABLE(p_ctx)) + p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + + p_ctx->priv->pf_close = flv_reader_close; + p_ctx->priv->pf_read = flv_reader_read; + p_ctx->priv->pf_seek = flv_reader_seek; + + return VC_CONTAINER_SUCCESS; + + error: + if(status == VC_CONTAINER_SUCCESS) status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + LOG_DEBUG(p_ctx, "flv: error opening stream"); + if(module) flv_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open flv_reader_open +#endif diff --git a/containers/h264/avc1_packetizer.c b/containers/h264/avc1_packetizer.c new file mode 100644 index 0000000..8082f0b --- /dev/null +++ b/containers/h264/avc1_packetizer.c @@ -0,0 +1,343 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** \file + * Implementation of an ISO 14496-15 to Annexe-B AVC video packetizer. + */ + +#include +#include + +#include "containers/packetizers.h" +#include "containers/core/packetizers_private.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_bytestream.h" + +#ifndef ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#endif + +/** Arbitrary number which should be sufficiently high so that no sane frame will + * be bigger than that. */ +#define MAX_FRAME_SIZE (1920*1088*2) + +VC_CONTAINER_STATUS_T avc1_packetizer_open( VC_PACKETIZER_T * ); + +/*****************************************************************************/ +typedef struct VC_PACKETIZER_MODULE_T { + enum { + STATE_FRAME_WAIT = 0, + STATE_BUFFER_INIT, + STATE_NAL_START, + STATE_NAL_DATA, + } state; + + unsigned int length_size; + + unsigned int frame_size; + unsigned int bytes_read; + unsigned int start_code_bytes_left; + unsigned int nal_bytes_left; + +} VC_PACKETIZER_MODULE_T; + +static const uint8_t h264_start_code[] = {0, 0, 0, 1}; + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avc1_packetizer_close( VC_PACKETIZER_T *p_ctx ) +{ + free(p_ctx->priv->module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avc1_packetizer_reset( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + module->state = STATE_FRAME_WAIT; + module->frame_size = 0; + module->bytes_read = 0; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avc1_packetizer_packetize( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_PACKET_T *out, VC_PACKETIZER_FLAGS_T flags) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_BYTESTREAM_T *stream = &p_ctx->priv->stream; + VC_CONTAINER_PACKET_T *packet; + unsigned int offset, size, nal_num; + uint8_t data[4]; + VC_CONTAINER_PARAM_UNUSED(nal_num); + + while(1) switch (module->state) + { + case STATE_FRAME_WAIT: + for (packet = stream->current, size = 0; + packet && !(packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_END); + packet = packet->next) + size += packet->size; + if (!packet) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; /* We need more data */ + + size += packet->size; + + /* We now have a complete frame available */ + + module->nal_bytes_left = 0; + module->start_code_bytes_left = 0; + + /* Find out the number of NAL units and size of the frame */ + for (offset = nal_num = 0; offset + module->length_size < size; nal_num++) + { + unsigned int nal_size; + + bytestream_peek_at(stream, offset, data, module->length_size); + offset += module->length_size; + + nal_size = data[0]; + if (module->length_size > 1) + nal_size = (nal_size << 8)|data[1]; + if (module->length_size > 2) + nal_size = (nal_size << 8)|data[2]; + if (module->length_size > 3) + nal_size = (nal_size << 8)|data[3]; + if (offset + nal_size > size) + nal_size = size - offset; + + offset += nal_size; + module->frame_size += nal_size + sizeof(h264_start_code); +#ifdef ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE + LOG_DEBUG(0, "nal unit size %u", nal_size); +#endif + } + LOG_DEBUG(0, "frame size: %u(%u/%u), pts: %"PRIi64, module->frame_size, + size, nal_num, stream->current->pts); + + /* fall through to the next state */ + module->state = STATE_BUFFER_INIT; + + case STATE_BUFFER_INIT: + packet = stream->current; + out->size = module->frame_size - module->bytes_read; + out->pts = out->dts = VC_CONTAINER_TIME_UNKNOWN; + out->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END; + + if (!module->bytes_read) + { + out->pts = packet->pts; + out->dts = packet->dts; + out->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + } + + if (flags & VC_PACKETIZER_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + if (flags & VC_PACKETIZER_FLAG_SKIP) + { + /* The easiest is to just drop all the packets belonging to the frame */ + while (!(stream->current->flags & VC_CONTAINER_PACKET_FLAG_FRAME_END)) + bytestream_skip_packet(stream); + bytestream_skip_packet(stream); + + module->frame_size = 0; + module->bytes_read = 0; + return VC_CONTAINER_SUCCESS; + } + + /* We now know that we'll have to read some data so reset the output size */ + out->size = 0; + + /* Go to the next relevant state */ + module->state = STATE_NAL_START; + if (module->nal_bytes_left || module->bytes_read == module->frame_size) + module->state = STATE_NAL_DATA; + break; + + case STATE_NAL_START: + /* Extract the size of the current NAL */ + bytestream_get(stream, data, module->length_size); + + module->nal_bytes_left = data[0]; + if (module->length_size > 1) + module->nal_bytes_left = (module->nal_bytes_left << 8)|data[1]; + if (module->length_size > 2) + module->nal_bytes_left = (module->nal_bytes_left << 8)|data[2]; + if (module->length_size > 3) + module->nal_bytes_left = (module->nal_bytes_left << 8)|data[3]; + + if (module->bytes_read + module->nal_bytes_left + sizeof(h264_start_code) > + module->frame_size) + { + LOG_ERROR(0, "truncating nal (%u/%u)", module->nal_bytes_left, + module->frame_size - module->bytes_read - sizeof(h264_start_code)); + module->nal_bytes_left = module->frame_size - sizeof(h264_start_code); + } + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE + LOG_DEBUG(0, "nal unit size %u", module->nal_bytes_left); +#endif + + module->start_code_bytes_left = sizeof(h264_start_code); + + /* fall through to the next state */ + module->state = STATE_NAL_DATA; + + case STATE_NAL_DATA: + /* Start by adding the start code */ + if (module->start_code_bytes_left) + { + size = MIN(out->buffer_size - out->size, module->start_code_bytes_left); + memcpy(out->data + out->size, h264_start_code + sizeof(h264_start_code) - + module->start_code_bytes_left, size); + module->start_code_bytes_left -= size; + module->bytes_read += size; + out->size += size; + } + + /* Then append the NAL unit itself */ + if (module->nal_bytes_left) + { + size = MIN(out->buffer_size - out->size, module->nal_bytes_left); + bytestream_get( stream, out->data + out->size, size ); + module->nal_bytes_left -= size; + module->bytes_read += size; + out->size += size; + } + + /* Check whether we're done */ + if (module->bytes_read == module->frame_size) + { + bytestream_skip_packet(stream); + module->state = STATE_FRAME_WAIT; + module->frame_size = 0; + module->bytes_read = 0; + return VC_CONTAINER_SUCCESS; + } + else if (out->buffer_size == out->size) + { + out->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + module->state = STATE_BUFFER_INIT; + return VC_CONTAINER_SUCCESS; + } + + /* We're not done, go to the next relevant state */ + module->state = STATE_NAL_START; + break; + + default: + break; + }; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T avc1_packetizer_codecconfig( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint8_t *out, *extra = p_ctx->in->extradata + 5; + uint8_t *extra_end = extra + p_ctx->in->extradata_size - 5; + unsigned int i, j, nal_size, out_size = 0; + + if (p_ctx->in->extradata_size <= 5 || + p_ctx->in->extradata[0] != 1 /* configurationVersion */) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + status = vc_container_format_extradata_alloc(p_ctx->out, p_ctx->in->extradata_size); + if (status != VC_CONTAINER_SUCCESS) + return status; + + out = p_ctx->out->extradata; + module->length_size = (*(p_ctx->in->extradata + 4) & 0x3) + 1; + + for (i = 0; i < 2 && extra < extra_end - 1; i++) + { + j = *(extra++) & (!i ? 0x1F : 0xFF); + for (; j > 0 && extra < extra_end - 2; j--) + { + nal_size = (extra[0] << 8) | extra[1]; extra += 2; + if (extra + nal_size > extra_end) + { + extra = extra_end; + break; + } + + out[0] = out[1] = out[2] = 0; out[3] = 1; + memcpy(out + 4, extra, nal_size); + out += nal_size + 4; extra += nal_size; + out_size += nal_size + 4; + } + } + + p_ctx->out->extradata_size = out_size; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T avc1_packetizer_open( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module; + VC_CONTAINER_STATUS_T status; + + if(p_ctx->in->codec != VC_CONTAINER_CODEC_H264 && + p_ctx->out->codec != VC_CONTAINER_CODEC_H264) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(p_ctx->in->codec_variant != VC_CONTAINER_VARIANT_H264_AVC1 && + p_ctx->out->codec_variant != VC_CONTAINER_VARIANT_H264_DEFAULT) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(!(p_ctx->in->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED)) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + p_ctx->priv->module = module = malloc(sizeof(*module)); + if(!module) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + memset(module, 0, sizeof(*module)); + + vc_container_format_copy(p_ctx->out, p_ctx->in, 0); + status = avc1_packetizer_codecconfig(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + { + free(module); + return status; + } + + p_ctx->out->codec_variant = VC_CONTAINER_VARIANT_H264_DEFAULT; + p_ctx->max_frame_size = MAX_FRAME_SIZE; + p_ctx->priv->pf_close = avc1_packetizer_close; + p_ctx->priv->pf_packetize = avc1_packetizer_packetize; + p_ctx->priv->pf_reset = avc1_packetizer_reset; + LOG_DEBUG(0, "using avc1 video packetizer"); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_PACKETIZER_REGISTER(avc1_packetizer_open, "avc1"); diff --git a/containers/io/io_file.c b/containers/io/io_file.c new file mode 100644 index 0000000..ea4b945 --- /dev/null +++ b/containers/io/io_file.c @@ -0,0 +1,154 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_uri.h" + +typedef struct VC_CONTAINER_IO_MODULE_T +{ + FILE *stream; + +} VC_CONTAINER_IO_MODULE_T; + +VC_CONTAINER_STATUS_T vc_container_io_file_open( VC_CONTAINER_IO_T *, const char *, + VC_CONTAINER_IO_MODE_T ); + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_file_close( VC_CONTAINER_IO_T *p_ctx ) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + fclose(module->stream); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t io_file_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + size_t ret = fread(buffer, 1, size, p_ctx->module->stream); + if(ret != size) + { + /* Sanity check return value. Some platforms (e.g. Android) can return -1 */ + if( ((int)ret) < 0 ) ret = 0; + + if( feof(p_ctx->module->stream) ) p_ctx->status = VC_CONTAINER_ERROR_EOS; + else p_ctx->status = VC_CONTAINER_ERROR_FAILED; + } + return ret; +} + +/*****************************************************************************/ +static size_t io_file_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size) +{ + return fwrite(buffer, 1, size, p_ctx->module->stream); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_file_seek(VC_CONTAINER_IO_T *p_ctx, int64_t offset) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int ret; + + //FIXME: large file support +#ifdef _VIDEOCORE + extern int fseek64(FILE *fp, int64_t offset, int whence); + ret = fseek64(p_ctx->module->stream, offset, SEEK_SET); +#else + if (offset > (int64_t)UINT_MAX) + { + p_ctx->status = VC_CONTAINER_ERROR_EOS; + return VC_CONTAINER_ERROR_EOS; + } + ret = fseek(p_ctx->module->stream, (long)offset, SEEK_SET); +#endif + if(ret) + { + if( feof(p_ctx->module->stream) ) status = VC_CONTAINER_ERROR_EOS; + else status = VC_CONTAINER_ERROR_FAILED; + } + + p_ctx->status = status; + return status; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_file_open( VC_CONTAINER_IO_T *p_ctx, + const char *unused, VC_CONTAINER_IO_MODE_T mode ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_IO_MODULE_T *module = 0; + const char *psz_mode = mode == VC_CONTAINER_IO_MODE_WRITE ? "wb+" : "rb"; + const char *uri = p_ctx->uri; + FILE *stream = 0; + VC_CONTAINER_PARAM_UNUSED(unused); + + if(vc_uri_path(p_ctx->uri_parts)) + uri = vc_uri_path(p_ctx->uri_parts); + + stream = fopen(uri, psz_mode); + if(!stream) { status = VC_CONTAINER_ERROR_URI_NOT_FOUND; goto error; } + + /* Turn off buffering. The container layer will provide its own cache */ + setvbuf(stream, NULL, _IONBF, 0); + + module = malloc( sizeof(*module) ); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + + p_ctx->module = module; + module->stream = stream; + p_ctx->pf_close = io_file_close; + p_ctx->pf_read = io_file_read; + p_ctx->pf_write = io_file_write; + p_ctx->pf_seek = io_file_seek; + + if(mode == VC_CONTAINER_IO_MODE_WRITE) + { + p_ctx->max_size = (1UL<<31)-1; /* For now limit to 2GB */ + } + else + { + //FIXME: large file support, platform-specific file size + fseek(p_ctx->module->stream, 0, SEEK_END); + p_ctx->size = ftell(p_ctx->module->stream); + fseek(p_ctx->module->stream, 0, SEEK_SET); + } + + p_ctx->capabilities = VC_CONTAINER_IO_CAPS_NO_CACHING; + return VC_CONTAINER_SUCCESS; + + error: + if(stream) fclose(stream); + return status; +} diff --git a/containers/io/io_http.c b/containers/io/io_http.c new file mode 100644 index 0000000..48d8661 --- /dev/null +++ b/containers/io/io_http.c @@ -0,0 +1,898 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_uri.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_list.h" +#include "containers/core/containers_utils.h" +#include "containers/net/net_sockets.h" + +/* Set to 1 if you want to log all HTTP requests */ +#define ENABLE_HTTP_EXTRA_LOGGING 0 + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +#define IO_HTTP_DEFAULT_PORT "80" + +/** Space for sending requests and receiving responses */ +#define COMMS_BUFFER_SIZE 4000 + +/** Largest allowed HTTP URI. Must be substantially smaller than COMMS_BUFFER_SIZE + * to allow for the headers that may be sent. */ +#define HTTP_URI_LENGTH_MAX 1024 + +/** Initial capacity of header list */ +#define HEADER_LIST_INITIAL_CAPACITY 16 + +/** Format of the first line of an HTTP request */ +#define HTTP_REQUEST_LINE_FORMAT "%s %s HTTP/1.1\r\nHost: %s\r\n" + +/** Format of a range request */ +#define HTTP_RANGE_REQUEST "Range: bytes=%"PRId64"-%"PRId64"\r\n" + +/** Format string for common headers used with all request methods. + * Note: includes double new line to terminate headers */ +#define TRAILING_HEADERS_FORMAT "User-Agent: Broadcom/1.0\r\n\r\n" + +/** \name HTTP methods, used as the first item in the request line + * @{ */ +#define GET_METHOD "GET" +#define HEAD_METHOD "HEAD" +/* @} */ + +/** \name Names of headers used by the code + * @{ */ +#define CONTENT_LENGTH_NAME "Content-Length" +#define CONTENT_BASE_NAME "Content-Base" +#define CONTENT_LOCATION_NAME "Content-Location" +#define ACCEPT_RANGES_NAME "Accept-Ranges" +#define CONNECTION_NAME "Connection" +/* @} */ + +/** Supported HTTP major version number */ +#define HTTP_MAJOR_VERSION 1 +/** Supported HTTP minor version number */ +#define HTTP_MINOR_VERSION 1 + +/** Lowest successful status code value */ +#define HTTP_STATUS_OK 200 +#define HTTP_STATUS_PARTIAL_CONTENT 206 + +typedef struct http_header_tag { + const char *name; + char *value; +} HTTP_HEADER_T; + + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_IO_MODULE_T +{ + VC_CONTAINER_NET_T *sock; + VC_CONTAINERS_LIST_T *header_list; /**< Parsed response headers, pointing into comms buffer */ + + bool persistent; + int64_t cur_offset; + bool reconnecting; + + /* Buffer used for sending and receiving HTTP messages */ + char comms_buffer[COMMS_BUFFER_SIZE]; +} VC_CONTAINER_IO_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ + +static int io_http_header_comparator(const HTTP_HEADER_T *first, const HTTP_HEADER_T *second); +static VC_CONTAINER_STATUS_T io_http_send(VC_CONTAINER_IO_T *p_ctx); + +VC_CONTAINER_STATUS_T vc_container_io_http_open(VC_CONTAINER_IO_T *, const char *, + VC_CONTAINER_IO_MODE_T); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/**************************************************************************//** + * Trim whitespace from the end and start of the string + * + * \param str String to be trimmed + * \return Trimmed string + */ +static char *io_http_trim(char *str) +{ + char *s = str + strlen(str); + + /* Search backwards for first non-whitespace */ + while (--s >= str &&(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) + ; /* Everything done in the while */ + s[1] = '\0'; + + /* Now move start of string forwards to first non-whitespace */ + s = str; + while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') + s++; + + return s; +} + +/**************************************************************************//** + * Header comparison function. + * Compare two header structures and return whether the first is less than, + * equal to or greater than the second. + * + * @param first The first structure to be compared. + * @param second The second structure to be compared. + * @return Negative if first is less than second, positive if first is greater + * and zero if they are equal. + */ +static int io_http_header_comparator(const HTTP_HEADER_T *first, const HTTP_HEADER_T *second) +{ + return strcasecmp(first->name, second->name); +} + +/**************************************************************************//** + * Check a response status line to see if the response is usable or not. + * Reasons for invalidity include: + * - Incorrectly formatted + * - Unsupported version + * - Status code is not in the 2xx range + * + * @param status_line The response status line. + * @return The resulting status of the function. + */ +static bool io_http_successful_response_status(const char *status_line) +{ + unsigned int major_version, minor_version, status_code; + + /* coverity[secure_coding] String is null-terminated */ + if (sscanf(status_line, "HTTP/%u.%u %u", &major_version, &minor_version, &status_code) != 3) + { + LOG_ERROR(NULL, "HTTP: Invalid response status line:\n%s", status_line); + return false; + } + + if (major_version != HTTP_MAJOR_VERSION || minor_version != HTTP_MINOR_VERSION) + { + LOG_ERROR(NULL, "HTTP: Unexpected response HTTP version: %u.%u", major_version, minor_version); + return false; + } + + if (status_code != HTTP_STATUS_OK && status_code != HTTP_STATUS_PARTIAL_CONTENT) + { + LOG_ERROR(NULL, "HTTP: Response status unsuccessful:\n%s", status_line); + return false; + } + + return true; +} + +/**************************************************************************//** + * Get the content length header from the response headers as an unsigned + * 64-bit integer. + * If the content length header is not found or badly formatted, zero is + * returned. + * + * @param header_list The response headers. + * @return The content length. + */ +static uint64_t io_http_get_content_length(VC_CONTAINERS_LIST_T *header_list) +{ + uint64_t content_length = 0; + HTTP_HEADER_T header; + + header.name = CONTENT_LENGTH_NAME; + if (header_list && vc_containers_list_find_entry(header_list, &header)) + /* coverity[secure_coding] String is null-terminated */ + sscanf(header.value, "%"PRIu64, &content_length); + + return content_length; +} + +/**************************************************************************//** + * Get the accept ranges header from the response headers and verify that + * the server accepts byte ranges.. + * If the accept ranges header is not found false is returned. + * + * @param header_list The response headers. + * @return The resulting status of the function. + */ +static bool io_http_check_accept_range(VC_CONTAINERS_LIST_T *header_list) +{ + HTTP_HEADER_T header; + + header.name = ACCEPT_RANGES_NAME; + if (header_list && vc_containers_list_find_entry(header_list, &header)) + { + /* coverity[secure_coding] String is null-terminated */ + if (!strcasecmp(header.value, "bytes")) + return true; + } + + return false; +} + +/**************************************************************************//** + * Check whether the server supports persistent connections. + * + * @param header_list The response headers. + * @return The resulting status of the function. + */ +static bool io_http_check_persistent_connection(VC_CONTAINERS_LIST_T *header_list) +{ + HTTP_HEADER_T header; + + header.name = CONNECTION_NAME; + if (header_list && vc_containers_list_find_entry(header_list, &header)) + { + /* coverity[secure_coding] String is null-terminated */ + if (!strcasecmp(header.value, "close")) + return false; + } + + return true; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T translate_net_status_to_container_status(vc_container_net_status_t net_status) +{ + switch (net_status) + { + case VC_CONTAINER_NET_SUCCESS: return VC_CONTAINER_SUCCESS; + case VC_CONTAINER_NET_ERROR_INVALID_SOCKET: return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + case VC_CONTAINER_NET_ERROR_NOT_ALLOWED: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + case VC_CONTAINER_NET_ERROR_INVALID_PARAMETER: return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + case VC_CONTAINER_NET_ERROR_NO_MEMORY: return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + case VC_CONTAINER_NET_ERROR_IN_USE: return VC_CONTAINER_ERROR_URI_OPEN_FAILED; + case VC_CONTAINER_NET_ERROR_NETWORK: return VC_CONTAINER_ERROR_EOS; + case VC_CONTAINER_NET_ERROR_CONNECTION_LOST: return VC_CONTAINER_ERROR_EOS; + case VC_CONTAINER_NET_ERROR_NOT_CONNECTED: return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + case VC_CONTAINER_NET_ERROR_TIMED_OUT: return VC_CONTAINER_ERROR_ABORTED; + case VC_CONTAINER_NET_ERROR_CONNECTION_REFUSED: return VC_CONTAINER_ERROR_NOT_FOUND; + case VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND: return VC_CONTAINER_ERROR_NOT_FOUND; + case VC_CONTAINER_NET_ERROR_TRY_AGAIN: return VC_CONTAINER_ERROR_CONTINUE; + default: return VC_CONTAINER_ERROR_FAILED; + } +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_http_open_socket(VC_CONTAINER_IO_T *ctx) +{ + VC_CONTAINER_IO_MODULE_T *module = ctx->module; + VC_CONTAINER_STATUS_T status; + const char *host, *port; + + /* Treat empty host or port strings as not defined */ + port = vc_uri_port(ctx->uri_parts); + if (port && !*port) + port = NULL; + + /* Require the port to be defined */ + if (!port) + { + status = VC_CONTAINER_ERROR_URI_OPEN_FAILED; + goto error; + } + + host = vc_uri_host(ctx->uri_parts); + if (host && !*host) + host = NULL; + + if (!host) + { + status = VC_CONTAINER_ERROR_URI_OPEN_FAILED; + goto error; + } + + module->sock = vc_container_net_open(host, port, VC_CONTAINER_NET_OPEN_FLAG_STREAM, NULL); + if (!module->sock) + { + status = VC_CONTAINER_ERROR_URI_NOT_FOUND; + goto error; + } + + return VC_CONTAINER_SUCCESS; + +error: + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_http_close_socket(VC_CONTAINER_IO_MODULE_T *module) +{ + if (module->sock) + { + vc_container_net_close(module->sock); + module->sock = NULL; + } + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t io_http_read_from_net(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + size_t ret; + vc_container_net_status_t net_status; + + ret = vc_container_net_read(p_ctx->module->sock, buffer, size); + net_status = vc_container_net_status(p_ctx->module->sock); + p_ctx->status = translate_net_status_to_container_status(net_status); + + return ret; +} + +/**************************************************************************//** + * Reads an HTTP response and parses it into headers and content. + * The headers and content remain stored in the comms buffer, but referenced + * by the module's header list. Content uses a special header name that cannot + * occur in the real headers. + * + * @param p_ctx The HTTP reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T io_http_read_response(VC_CONTAINER_IO_T *p_ctx) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + char *next_read = module->comms_buffer; + size_t space_available = sizeof(module->comms_buffer) - 1; /* Allow for a NUL */ + char *ptr = next_read; + bool end_response = false; + HTTP_HEADER_T header; + const char endstr[] = "\r\n\r\n"; + int endcount = sizeof(endstr) - 1; + int endchk = 0; + + vc_containers_list_reset(module->header_list); + + /* Response status line doesn't need to be stored, just checked */ + header.name = NULL; + header.value = next_read; + + /* + * We need to read just a byte at a time to make sure that we just read the HTTP response and + * no more. For example, if a GET operation was requested the file being fetched will also + * be waiting to be read on the socket. + */ + + while (space_available) + { + if (io_http_read_from_net(p_ctx, next_read, 1) != 1) + break; + + next_read++; + space_available--; + + if (next_read[-1] == endstr[endchk]) + { + if (++endchk == endcount) + break; + } + else + endchk = 0; + } + if (!space_available) + { + LOG_ERROR(NULL, "comms buffer too small for complete HTTP message (%d)", + sizeof(module->comms_buffer)); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + *next_read = '\0'; + + if (endchk == endcount) + { + if (ENABLE_HTTP_EXTRA_LOGGING) + LOG_DEBUG(NULL, "READ FROM SERVER: %d bytes\n%s\n-----------------------------------------", + sizeof(module->comms_buffer) - 1 - space_available, module->comms_buffer); + + while (!end_response && ptr < next_read) + { + switch (*ptr) + { + case ':': + if (header.value) + { + /* Just another character in the value */ + ptr++; + } else { + /* End of name, expect value next */ + *ptr++ = '\0'; + header.value = ptr; + } + break; + + case '\n': + if (header.value) + { + /* End of line while parsing the value part of the header, add name/value pair to list */ + *ptr++ = '\0'; + header.value = io_http_trim(header.value); + if (header.name) + { + if (!vc_containers_list_insert(module->header_list, &header, false)) + { + LOG_ERROR(NULL, "HTTP: Failed to add <%s> header to list", header.name); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + } else { + /* Check response status line */ + if (!io_http_successful_response_status(header.value)) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + /* Ready for next header */ + header.name = ptr; + header.value = NULL; + } else { + /* End of line while parsing the name of a header */ + *ptr++ = '\0'; + if (*header.name && *header.name != '\r') + { + /* A non-empty name is invalid, so fail */ + LOG_ERROR(NULL, "HTTP: Invalid name in header - no colon:\n%s", header.name); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + /* An empty name signifies the end of the HTTP response */ + end_response = true; + } + break; + + default: + /* Just another character in either the name or the value */ + ptr++; + } + } + } + + if (!space_available && !end_response) + { + /* Ran out of buffer space */ + LOG_ERROR(NULL, "HTTP: Response header section too big"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + return p_ctx->status; +} + +/**************************************************************************//** + * Send a GET request to the HTTP server. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T io_http_send_get_request(VC_CONTAINER_IO_T *p_ctx, size_t size) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + char *ptr = module->comms_buffer, *end = ptr + sizeof(module->comms_buffer); + int64_t end_offset; + + ptr += snprintf(ptr, end - ptr, HTTP_REQUEST_LINE_FORMAT, GET_METHOD, + vc_uri_path(p_ctx->uri_parts), vc_uri_host(p_ctx->uri_parts)); + + end_offset = module->cur_offset + size - 1; + if (end_offset >= p_ctx->size) + end_offset = p_ctx->size - 1; + + if (ptr < end) + ptr += snprintf(ptr, end - ptr, HTTP_RANGE_REQUEST, module->cur_offset, end_offset); + + if (ptr < end) + ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT); + + if (ptr >= end) + { + LOG_ERROR(0, "comms buffer too small (%i/%u)", (int)(end - ptr), + sizeof(module->comms_buffer)); + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + } + + if (ENABLE_HTTP_EXTRA_LOGGING) + LOG_DEBUG(NULL, "Sending server read request:\n%s\n---------------------\n", module->comms_buffer); + return io_http_send(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_http_seek(VC_CONTAINER_IO_T *p_ctx, int64_t offset) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + + /* + * No seeking past the end of the file. + */ + + if (offset < 0 || offset > p_ctx->size) + { + p_ctx->status = VC_CONTAINER_ERROR_EOS; + return VC_CONTAINER_ERROR_EOS; + } + + module->cur_offset = offset; + p_ctx->status = VC_CONTAINER_SUCCESS; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_http_close(VC_CONTAINER_IO_T *p_ctx) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + + if (!module) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + io_http_close_socket(module); + if (module->header_list) + vc_containers_list_destroy(module->header_list); + + free(module); + p_ctx->module = NULL; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t io_http_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + size_t content_length; + size_t bytes_read; + size_t ret = 0; + char *ptr = buffer; + + /* + * Are we at the end of the file? + */ + + if (module->cur_offset >= p_ctx->size) + { + p_ctx->status = VC_CONTAINER_ERROR_EOS; + return 0; + } + + if (!module->persistent) + { + status = io_http_open_socket(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_ERROR(NULL, "Error opening socket for GET request"); + return status; + } + } + + /* Send GET request and get response */ + status = io_http_send_get_request(p_ctx, size); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_ERROR(NULL, "Error sending GET request"); + goto error; + } + + status = io_http_read_response(p_ctx); + if (status == VC_CONTAINER_ERROR_EOS && !module->reconnecting) + { + LOG_DEBUG(NULL, "reconnecting"); + io_http_close_socket(module); + status = io_http_open_socket(p_ctx); + if (status == VC_CONTAINER_SUCCESS) + { + module->reconnecting = true; + status = io_http_read(p_ctx, buffer, size); + module->reconnecting = false; + return status; + } + } + if (status != VC_CONTAINER_SUCCESS) + { + LOG_ERROR(NULL, "Error reading GET response"); + goto error; + } + + /* + * How much data is the server offering us? + */ + + content_length = (size_t)io_http_get_content_length(module->header_list); + if (content_length > size) + { + LOG_ERROR(NULL, "received too much data (%i/%i)", + (int)content_length, (int)size); + status = VC_CONTAINER_ERROR_CORRUPTED; + goto error; + } + + bytes_read = 0; + while (bytes_read < content_length && p_ctx->status == VC_CONTAINER_SUCCESS) + { + ret = io_http_read_from_net(p_ctx, ptr, content_length - bytes_read); + if (p_ctx->status == VC_CONTAINER_SUCCESS) + { + bytes_read += ret; + ptr += ret; + } + } + + if (p_ctx->status == VC_CONTAINER_SUCCESS) + { + module->cur_offset += bytes_read; + ret = bytes_read; + } + + if (!module->persistent) + io_http_close_socket(module); + + return ret; + +error: + if (!module->persistent) + io_http_close_socket(module); + + return status; +} + +/*****************************************************************************/ +static size_t io_http_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size) +{ + size_t ret = vc_container_net_write(p_ctx->module->sock, buffer, size); + vc_container_net_status_t net_status; + + net_status = vc_container_net_status(p_ctx->module->sock); + p_ctx->status = translate_net_status_to_container_status(net_status); + + return ret; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_http_control(struct VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_CONTROL_T operation, + va_list args) +{ + vc_container_net_status_t net_status; + VC_CONTAINER_STATUS_T status; + + switch (operation) + { + case VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE: + net_status = vc_container_net_control(p_ctx->module->sock, VC_CONTAINER_NET_CONTROL_SET_READ_BUFFER_SIZE, args); + break; + case VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS: + net_status = vc_container_net_control(p_ctx->module->sock, VC_CONTAINER_NET_CONTROL_SET_READ_TIMEOUT_MS, args); + break; + default: + net_status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + } + + status = translate_net_status_to_container_status(net_status); + p_ctx->status = status; + + return status; +} + +/**************************************************************************//** + * Send out the data in the comms buffer. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T io_http_send(VC_CONTAINER_IO_T *p_ctx) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + size_t to_write; + size_t written; + const char *buffer = module->comms_buffer; + + to_write = strlen(buffer); + + while (to_write) + { + written = io_http_write(p_ctx, buffer, to_write); + if (p_ctx->status != VC_CONTAINER_SUCCESS) + break; + + to_write -= written; + buffer += written; + } + + return p_ctx->status; +} + +/**************************************************************************//** + * Send a HEAD request to the HTTP server. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T io_http_send_head_request(VC_CONTAINER_IO_T *p_ctx) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + char *ptr = module->comms_buffer, *end = ptr + sizeof(module->comms_buffer); + + ptr += snprintf(ptr, end - ptr, HTTP_REQUEST_LINE_FORMAT, HEAD_METHOD, + vc_uri_path(p_ctx->uri_parts), vc_uri_host(p_ctx->uri_parts)); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT); + + if (ptr >= end) + { + LOG_ERROR(0, "comms buffer too small (%i/%u)", (int)(end - ptr), + sizeof(module->comms_buffer)); + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + } + + return io_http_send(p_ctx); +} + +static VC_CONTAINER_STATUS_T io_http_head(VC_CONTAINER_IO_T *p_ctx) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + uint64_t content_length; + + /* Send HEAD request and get response */ + status = io_http_send_head_request(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + return status; + status = io_http_read_response(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + return status; + + /* + * Save the content length since that's our file size. + */ + + content_length = io_http_get_content_length(module->header_list); + if (content_length) + { + p_ctx->size = content_length; + LOG_DEBUG(NULL, "File size is %"PRId64, p_ctx->size); + } + + /* + * Now make sure that the server supports byte range requests. + */ + + if (!io_http_check_accept_range(module->header_list)) + { + LOG_ERROR(NULL, "Server doesn't support byte range requests"); + return VC_CONTAINER_ERROR_FAILED; + } + + /* + * Does it support persistent connections? + */ + + if (io_http_check_persistent_connection(module->header_list)) + { + module->persistent = true; + } + else + { + LOG_DEBUG(NULL, "Server does not support persistent connections"); + io_http_close_socket(module); + } + + module->cur_offset = 0; + + return status; +} + +/***************************************************************************** +Functions exported as part of the I/O Module API + *****************************************************************************/ + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_http_open(VC_CONTAINER_IO_T *p_ctx, + const char *unused, VC_CONTAINER_IO_MODE_T mode) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_IO_MODULE_T *module = 0; + VC_CONTAINER_PARAM_UNUSED(unused); + + /* Check the URI to see if we're dealing with an http stream */ + if (!vc_uri_scheme(p_ctx->uri_parts) || + strcasecmp(vc_uri_scheme(p_ctx->uri_parts), "http")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* + * Some basic error checking. + */ + + if (mode == VC_CONTAINER_IO_MODE_WRITE) + { + status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + goto error; + } + + if (strlen(p_ctx->uri) > HTTP_URI_LENGTH_MAX) + { + status = VC_CONTAINER_ERROR_URI_OPEN_FAILED; + goto error; + } + + module = calloc(1, sizeof(*module)); + if (!module) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto error; + } + p_ctx->module = module; + + /* header_list will contain pointers into the response_buffer, so take care in re-use */ + module->header_list = vc_containers_list_create(HEADER_LIST_INITIAL_CAPACITY, sizeof(HTTP_HEADER_T), + (VC_CONTAINERS_LIST_COMPARATOR_T)io_http_header_comparator); + if (!module->header_list) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto error; + } + + /* + * Make sure that we have a port number. + */ + + if (vc_uri_port(p_ctx->uri_parts) == NULL) + vc_uri_set_port(p_ctx->uri_parts, IO_HTTP_DEFAULT_PORT); + + status = io_http_open_socket(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + /* + * Whoo hoo! Our socket is open. Now let's send a HEAD request. + */ + + status = io_http_head(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + p_ctx->pf_close = io_http_close; + p_ctx->pf_read = io_http_read; + p_ctx->pf_write = NULL; + p_ctx->pf_control = io_http_control; + p_ctx->pf_seek = io_http_seek; + + p_ctx->capabilities = VC_CONTAINER_IO_CAPS_NO_CACHING; + p_ctx->capabilities |= VC_CONTAINER_IO_CAPS_SEEK_SLOW; + + return VC_CONTAINER_SUCCESS; + +error: + io_http_close(p_ctx); + return status; +} diff --git a/containers/io/io_net.c b/containers/io/io_net.c new file mode 100644 index 0000000..a1912e5 --- /dev/null +++ b/containers/io/io_net.c @@ -0,0 +1,379 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_uri.h" +#include "containers/net/net_sockets.h" + +/* Uncomment this macro definition to capture data read and written through this interface */ +/* #define IO_NET_CAPTURE_PACKETS */ + +#ifdef IO_NET_CAPTURE_PACKETS +#include + +#ifdef ENABLE_CONTAINERS_STANDALONE +#ifdef _MSC_VER +#define IO_NET_CAPTURE_PREFIX "C:\\" +#else /* !_MSC_VER */ +#define IO_NET_CAPTURE_PREFIX "~/" +#endif +#else /* !ENABLE_CONTAINERS_STANDALONE */ +#define IO_NET_CAPTURE_PREFIX "/mfs/sd/" +#endif + +#define IO_NET_CAPTURE_READ_FILE "capture_read_%s_%s%c.pkt" +#define IO_NET_CAPTURE_WRITE_FILE "capture_write_%s_%s%c.pkt" +#define IO_NET_CAPTURE_READ_FORMAT IO_NET_CAPTURE_PREFIX IO_NET_CAPTURE_READ_FILE +#define IO_NET_CAPTURE_WRITE_FORMAT IO_NET_CAPTURE_PREFIX IO_NET_CAPTURE_WRITE_FILE + +#define CAPTURE_FILENAME_BUFFER_SIZE 300 + +#define CAPTURE_BUFFER_SIZE 65536 + +/** Native byte order word */ +#define NATIVE_BYTE_ORDER 0x50415753 +#endif + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_IO_MODULE_T +{ + VC_CONTAINER_NET_T *sock; +#ifdef IO_NET_CAPTURE_PACKETS + FILE *read_capture_file; + FILE *write_capture_file; +#endif +} VC_CONTAINER_IO_MODULE_T; + +/** List of recognised network URI schemes (TCP or UDP). + * Note: always use lower case for the scheme name. */ +static struct +{ + const char *scheme; + bool is_udp; +} recognised_schemes[] = { + { "rtp:", true }, + { "rtsp:", false }, +}; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_net_open( VC_CONTAINER_IO_T *, const char *, + VC_CONTAINER_IO_MODE_T ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +#ifdef IO_NET_CAPTURE_PACKETS +/*****************************************************************************/ +static FILE *io_net_open_capture_file(const char *host_str, + const char *port_str, + bool is_udp, + VC_CONTAINER_IO_MODE_T mode) +{ + char filename[CAPTURE_FILENAME_BUFFER_SIZE]; + const char *format; + FILE *stream = NULL; + uint32_t byte_order = NATIVE_BYTE_ORDER; + + switch (mode) + { + case VC_CONTAINER_IO_MODE_WRITE: + format = IO_NET_CAPTURE_WRITE_FORMAT; + break; + case VC_CONTAINER_IO_MODE_READ: + format = IO_NET_CAPTURE_READ_FORMAT; + break; + default: + /* Invalid mode */ + return NULL; + } + + if (!host_str) + host_str = ""; + if (!port_str) + port_str = ""; + + /* Check filename will fit in buffer */ + if (strlen(format) + strlen(host_str) + strlen(port_str) - 4 > CAPTURE_FILENAME_BUFFER_SIZE) + return NULL; + + /* Create the file */ + sprintf(filename, format, host_str, port_str, is_udp ? 'u' : 't'); + stream = fopen(filename, "wb"); + if (!stream) + return NULL; + + /* Buffer plenty of data at a time, if possible */ + setvbuf(stream, NULL, _IOFBF, CAPTURE_BUFFER_SIZE); + + /* Start file with a byte order marker */ + if (fwrite(&byte_order, 1, sizeof(byte_order), stream) != sizeof(byte_order)) + { + /* Failed to write even just the byte order mark - abort */ + fclose(stream); + stream = NULL; + remove(filename); + } + + return stream; +} + +/*****************************************************************************/ +static void io_net_capture_write_packet( FILE *stream, + const char *buffer, + uint32_t buffer_size ) +{ + if (stream && buffer && buffer_size) + { + fwrite(&buffer_size, 1, sizeof(buffer_size), stream); + fwrite(buffer, 1, buffer_size, stream); + } +} +#endif + +/*****************************************************************************/ +static bool io_net_recognise_scheme(const char *uri, bool *is_udp) +{ + size_t ii; + const char *scheme; + + if (!uri) + return false; + + for (ii = 0; ii < countof(recognised_schemes); ii++) + { + scheme = recognised_schemes[ii].scheme; + if (strncmp(scheme, uri, strlen(scheme)) == 0) + { + *is_udp = recognised_schemes[ii].is_udp; + return true; + } + } + + return false; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T translate_net_status_to_container_status(vc_container_net_status_t net_status) +{ + switch (net_status) + { + case VC_CONTAINER_NET_SUCCESS: return VC_CONTAINER_SUCCESS; + case VC_CONTAINER_NET_ERROR_INVALID_SOCKET: return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + case VC_CONTAINER_NET_ERROR_NOT_ALLOWED: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + case VC_CONTAINER_NET_ERROR_INVALID_PARAMETER: return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + case VC_CONTAINER_NET_ERROR_NO_MEMORY: return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + case VC_CONTAINER_NET_ERROR_IN_USE: return VC_CONTAINER_ERROR_URI_OPEN_FAILED; + case VC_CONTAINER_NET_ERROR_NETWORK: return VC_CONTAINER_ERROR_EOS; + case VC_CONTAINER_NET_ERROR_CONNECTION_LOST: return VC_CONTAINER_ERROR_EOS; + case VC_CONTAINER_NET_ERROR_NOT_CONNECTED: return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + case VC_CONTAINER_NET_ERROR_TIMED_OUT: return VC_CONTAINER_ERROR_ABORTED; + case VC_CONTAINER_NET_ERROR_CONNECTION_REFUSED: return VC_CONTAINER_ERROR_NOT_FOUND; + case VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND: return VC_CONTAINER_ERROR_NOT_FOUND; + case VC_CONTAINER_NET_ERROR_TRY_AGAIN: return VC_CONTAINER_ERROR_CONTINUE; + default: return VC_CONTAINER_ERROR_FAILED; + } +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_net_close( VC_CONTAINER_IO_T *p_ctx ) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + + if (!module) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + if (module->sock) + vc_container_net_close(module->sock); +#ifdef IO_NET_CAPTURE_PACKETS + if (module->read_capture_file) + fclose(module->read_capture_file); + if (module->write_capture_file) + fclose(module->write_capture_file); +#endif + free(module); + p_ctx->module = NULL; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t io_net_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + size_t ret = vc_container_net_read(p_ctx->module->sock, buffer, size); + vc_container_net_status_t net_status; + + net_status = vc_container_net_status(p_ctx->module->sock); + p_ctx->status = translate_net_status_to_container_status(net_status); + +#ifdef IO_NET_CAPTURE_PACKETS + if (p_ctx->status == VC_CONTAINER_SUCCESS) + io_net_capture_write_packet(p_ctx->module->read_capture_file, (const char *)buffer, ret); +#endif + + return ret; +} + +/*****************************************************************************/ +static size_t io_net_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size) +{ + size_t ret = vc_container_net_write(p_ctx->module->sock, buffer, size); + vc_container_net_status_t net_status; + + net_status = vc_container_net_status(p_ctx->module->sock); + p_ctx->status = translate_net_status_to_container_status(net_status); + +#ifdef IO_NET_CAPTURE_PACKETS + if (p_ctx->status == VC_CONTAINER_SUCCESS) + io_net_capture_write_packet(p_ctx->module->write_capture_file, (const char *)buffer, ret); +#endif + + return ret; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_net_control(struct VC_CONTAINER_IO_T *p_ctx, + VC_CONTAINER_CONTROL_T operation, + va_list args) +{ + vc_container_net_status_t net_status; + VC_CONTAINER_STATUS_T status; + + switch (operation) + { + case VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE: + net_status = vc_container_net_control(p_ctx->module->sock, VC_CONTAINER_NET_CONTROL_SET_READ_BUFFER_SIZE, args); + break; + case VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS: + net_status = vc_container_net_control(p_ctx->module->sock, VC_CONTAINER_NET_CONTROL_SET_READ_TIMEOUT_MS, args); + break; + default: + net_status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + } + + status = translate_net_status_to_container_status(net_status); + p_ctx->status = status; + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_net_open_socket(VC_CONTAINER_IO_T *ctx, + VC_CONTAINER_IO_MODE_T mode, bool is_udp) +{ + VC_CONTAINER_IO_MODULE_T *module = ctx->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + const char *host, *port; + + /* Treat empty host or port strings as not defined */ + port = vc_uri_port(ctx->uri_parts); + if (port && !*port) + port = NULL; + + /* Require the port to be defined */ + if (!port) { status = VC_CONTAINER_ERROR_URI_OPEN_FAILED; goto error; } + + host = vc_uri_host(ctx->uri_parts); + if (host && !*host) + host = NULL; + + if (!host) + { + /* TCP servers cannot be handled by this interface and UDP senders need a target */ + if (!is_udp || mode == VC_CONTAINER_IO_MODE_WRITE) + { + status = VC_CONTAINER_ERROR_URI_OPEN_FAILED; + goto error; + } + } + + module->sock = vc_container_net_open(host, port, is_udp ? 0 : VC_CONTAINER_NET_OPEN_FLAG_STREAM, NULL); + if (!module->sock) { status = VC_CONTAINER_ERROR_URI_NOT_FOUND; goto error; } + +#ifdef IO_NET_CAPTURE_PACKETS + if (!is_udp || mode == VC_CONTAINER_IO_MODE_READ) + module->read_capture_file = io_net_open_capture_file(host, port, is_udp, VC_CONTAINER_IO_MODE_READ); + if (!is_udp || mode == VC_CONTAINER_IO_MODE_WRITE) + module->write_capture_file = io_net_open_capture_file(host, port, is_udp, VC_CONTAINER_IO_MODE_WRITE); +#endif + +error: + return status; +} + +/***************************************************************************** +Functions exported as part of the I/O Module API + *****************************************************************************/ + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_net_open( VC_CONTAINER_IO_T *p_ctx, + const char *unused, VC_CONTAINER_IO_MODE_T mode ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_IO_MODULE_T *module = 0; + bool is_udp; + VC_CONTAINER_PARAM_UNUSED(unused); + + if (!io_net_recognise_scheme(p_ctx->uri, &is_udp)) + { status = VC_CONTAINER_ERROR_URI_NOT_FOUND; goto error; } + + module = (VC_CONTAINER_IO_MODULE_T *)malloc( sizeof(*module) ); + if (!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->module = module; + + status = io_net_open_socket(p_ctx, mode, is_udp); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + p_ctx->pf_close = io_net_close; + p_ctx->pf_read = io_net_read; + p_ctx->pf_write = io_net_write; + p_ctx->pf_control = io_net_control; + + /* Disable caching, as this will block waiting for enough data to fill the cache or an error */ + p_ctx->capabilities = VC_CONTAINER_IO_CAPS_CANT_SEEK; + + return VC_CONTAINER_SUCCESS; + +error: + io_net_close(p_ctx); + return status; +} diff --git a/containers/io/io_null.c b/containers/io/io_null.c new file mode 100644 index 0000000..cd83bb8 --- /dev/null +++ b/containers/io/io_null.c @@ -0,0 +1,91 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_uri.h" + +VC_CONTAINER_STATUS_T vc_container_io_null_open( VC_CONTAINER_IO_T *, const char *, + VC_CONTAINER_IO_MODE_T ); + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_null_close( VC_CONTAINER_IO_T *p_ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t io_null_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(buffer); + VC_CONTAINER_PARAM_UNUSED(size); + return size; +} + +/*****************************************************************************/ +static size_t io_null_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(buffer); + VC_CONTAINER_PARAM_UNUSED(size); + return size; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_null_seek(VC_CONTAINER_IO_T *p_ctx, int64_t offset) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(offset); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_null_open( VC_CONTAINER_IO_T *p_ctx, + const char *unused, VC_CONTAINER_IO_MODE_T mode ) +{ + VC_CONTAINER_PARAM_UNUSED(unused); + VC_CONTAINER_PARAM_UNUSED(mode); + + /* Check the URI */ + if (!vc_uri_scheme(p_ctx->uri_parts) || + (strcasecmp(vc_uri_scheme(p_ctx->uri_parts), "null") && + strcasecmp(vc_uri_scheme(p_ctx->uri_parts), "null"))) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + p_ctx->pf_close = io_null_close; + p_ctx->pf_read = io_null_read; + p_ctx->pf_write = io_null_write; + p_ctx->pf_seek = io_null_seek; + return VC_CONTAINER_SUCCESS; +} diff --git a/containers/io/io_pktfile.c b/containers/io/io_pktfile.c new file mode 100644 index 0000000..7f4defb --- /dev/null +++ b/containers/io/io_pktfile.c @@ -0,0 +1,261 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_io.h" +#include "containers/core/containers_uri.h" + +/** Native byte order word */ +#define NATIVE_BYTE_ORDER 0x50415753 +/** Reverse of native byte order - need to swap bytes around */ +#define SWAP_BYTE_ORDER 0x53574150 + +typedef struct VC_CONTAINER_IO_MODULE_T +{ + FILE *stream; + bool is_native_order; +} VC_CONTAINER_IO_MODULE_T; + +/** List of recognised schemes. + * Note: always use lower case for the scheme name. */ +static const char * recognised_schemes[] = { + "rtp", "rtppkt", "rtsp", "rtsppkt", "pktfile", +}; + +VC_CONTAINER_STATUS_T vc_container_io_pktfile_open( VC_CONTAINER_IO_T *, const char *, + VC_CONTAINER_IO_MODE_T ); + +/*****************************************************************************/ +static bool recognise_scheme(const char *scheme) +{ + size_t ii; + + if (!scheme) + return false; + + for (ii = 0; ii < countof(recognised_schemes); ii++) + { + if (strcmp(recognised_schemes[ii], scheme) == 0) + return true; + } + + return false; +} + +/*****************************************************************************/ +static uint32_t swap_byte_order( uint32_t value ) +{ + /* Reverse the order of the bytes in the word */ + return ((value << 24) | ((value & 0xFF00) << 8) | ((value >> 8) & 0xFF00) | (value >> 24)); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T io_pktfile_close( VC_CONTAINER_IO_T *p_ctx ) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + fclose(module->stream); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t io_pktfile_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + VC_CONTAINER_IO_MODULE_T *module = p_ctx->module; + uint32_t length = 0; + size_t ret; + + ret = fread(&length, 1, sizeof(length), module->stream); + if (ret != sizeof(length)) + { + if( feof(module->stream) ) p_ctx->status = VC_CONTAINER_ERROR_EOS; + else p_ctx->status = VC_CONTAINER_ERROR_FAILED; + return 0; + } + + if (!module->is_native_order) + length = swap_byte_order(length); + + if (length > 1<<20) + { + p_ctx->status = VC_CONTAINER_ERROR_FAILED; + return 0; + } + + if (size > length) + size = length; + ret = fread(buffer, 1, size, module->stream); + if(ret != size) + { + if( feof(module->stream) ) p_ctx->status = VC_CONTAINER_ERROR_EOS; + else p_ctx->status = VC_CONTAINER_ERROR_FAILED; + } + else if (length > size) + { + /* Not enough space to read all the packet, so skip to the next one. */ + length -= size; + vc_container_assert((long)length > 0); + fseek(module->stream, (long)length, SEEK_CUR); + } + + return ret; +} + +/*****************************************************************************/ +static size_t io_pktfile_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size) +{ + uint32_t size_word; + size_t ret; + + if (size >= 0xFFFFFFFFUL) + size_word = 0xFFFFFFFFUL; + else + size_word = (uint32_t)size; + + ret = fwrite(&size_word, 1, sizeof(size_word), p_ctx->module->stream); + if (ret != sizeof(size_word)) + { + p_ctx->status = VC_CONTAINER_ERROR_FAILED; + return 0; + } + + ret = fwrite(buffer, 1, size_word, p_ctx->module->stream); + if (ret != size_word) + p_ctx->status = VC_CONTAINER_ERROR_FAILED; + if (fflush(p_ctx->module->stream) != 0) + p_ctx->status = VC_CONTAINER_ERROR_FAILED; + + return ret; +} + +/*****************************************************************************/ +static FILE *open_file(VC_CONTAINER_IO_T *ctx, VC_CONTAINER_IO_MODE_T mode, + VC_CONTAINER_STATUS_T *p_status) +{ + const char *psz_mode = mode == VC_CONTAINER_IO_MODE_WRITE ? "wb+" : "rb"; + FILE *stream = 0; + const char *port, *path; + + /* Treat empty port or path strings as not defined */ + port = vc_uri_port(ctx->uri_parts); + if (port && !*port) + port = NULL; + + path = vc_uri_path(ctx->uri_parts); + if (path && !*path) + path = NULL; + + /* Require the port to be undefined and the path to be defined */ + if (port || !path) { *p_status = VC_CONTAINER_ERROR_URI_OPEN_FAILED; goto error; } + + if (!recognise_scheme(vc_uri_scheme(ctx->uri_parts))) + { *p_status = VC_CONTAINER_ERROR_URI_NOT_FOUND; goto error; } + + stream = fopen(path, psz_mode); + if(!stream) { *p_status = VC_CONTAINER_ERROR_URI_NOT_FOUND; goto error; } + + *p_status = VC_CONTAINER_SUCCESS; + return stream; + +error: + return NULL; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T write_byte_order(FILE *stream) +{ + /* Simple byte order header word */ + uint32_t value = NATIVE_BYTE_ORDER; + + if (fwrite(&value, 1, sizeof(value), stream) != sizeof(value)) + return VC_CONTAINER_ERROR_OUT_OF_SPACE; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T read_byte_order(FILE *stream, bool *is_native) +{ + uint32_t value; + + if (fread(&value, 1, sizeof(value), stream) != sizeof(value)) + return VC_CONTAINER_ERROR_EOS; + + switch (value) + { + case NATIVE_BYTE_ORDER: *is_native = true; break; + case SWAP_BYTE_ORDER: *is_native = false; break; + default: return VC_CONTAINER_ERROR_CORRUPTED; + } + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T vc_container_io_pktfile_open( VC_CONTAINER_IO_T *p_ctx, + const char *unused, VC_CONTAINER_IO_MODE_T mode ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_IO_MODULE_T *module = 0; + FILE *stream = 0; + bool is_native_order = true; + VC_CONTAINER_PARAM_UNUSED(unused); + + stream = open_file(p_ctx, mode, &status); + if (status != VC_CONTAINER_SUCCESS) goto error; + + if (mode == VC_CONTAINER_IO_MODE_WRITE) + status = write_byte_order(stream); + else + status = read_byte_order(stream, &is_native_order); + if (status != VC_CONTAINER_SUCCESS) goto error; + + module = malloc( sizeof(*module) ); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + + p_ctx->module = module; + module->stream = stream; + module->is_native_order = is_native_order; + p_ctx->pf_close = io_pktfile_close; + p_ctx->pf_read = io_pktfile_read; + p_ctx->pf_write = io_pktfile_write; + + /* Do not allow caching by I/O core, as this will merge packets in the cache. */ + p_ctx->capabilities = VC_CONTAINER_IO_CAPS_CANT_SEEK; + return VC_CONTAINER_SUCCESS; + +error: + if(stream) fclose(stream); + return status; +} diff --git a/containers/metadata/id3/CMakeLists.txt b/containers/metadata/id3/CMakeLists.txt new file mode 100644 index 0000000..30178f8 --- /dev/null +++ b/containers/metadata/id3/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_metadata_id3 ${LIBRARY_TYPE} id3_metadata_reader.c) + +target_link_libraries(reader_metadata_id3 containers) + +install(TARGETS reader_metadata_id3 DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/metadata/id3/id3_metadata_reader.c b/containers/metadata/id3/id3_metadata_reader.c new file mode 100644 index 0000000..549273b --- /dev/null +++ b/containers/metadata/id3/id3_metadata_reader.c @@ -0,0 +1,450 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include + +#define CONTAINER_IS_BIG_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#define CONTAINER_HELPER_LOG_INDENT(a) 0 +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +#include "id3_metadata_strings.h" + +/****************************************************************************** +Defines +******************************************************************************/ +#define ID3_SYNC_SAFE(x) ((((x >> 24) & 0x7f) << 21) | (((x >> 16) & 0x7f) << 14) | \ + (((x >> 8) & 0x7f) << 7) | (((x >> 0) & 0x7f) << 0)) + +/****************************************************************************** +Type definitions +******************************************************************************/ + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T id3_metadata_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ +static VC_CONTAINER_METADATA_T *id3_metadata_append( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_METADATA_KEY_T key, + unsigned int size ) +{ + VC_CONTAINER_METADATA_T *meta, **p_meta; + unsigned int i; + + for (i = 0; i != p_ctx->meta_num; ++i) + { + if (key == p_ctx->meta[i]->key) break; + } + + /* Avoid duplicate entries for now */ + if (i < p_ctx->meta_num) return NULL; + + /* Sanity check size, truncate if necessary */ + size = MIN(size, 512); + + /* Allocate a new metadata entry */ + if((meta = malloc(sizeof(VC_CONTAINER_METADATA_T) + size)) == NULL) + return NULL; + + /* We need to grow the array holding the metadata entries somehow, ideally, + we'd like to use a linked structure of some sort but realloc is probably + okay in this case */ + if((p_meta = realloc(p_ctx->meta, sizeof(VC_CONTAINER_METADATA_T *) * (p_ctx->meta_num + 1))) == NULL) + { + free(meta); + return NULL; + } + + p_ctx->meta = p_meta; + memset(meta, 0, sizeof(VC_CONTAINER_METADATA_T) + size); + p_ctx->meta[p_ctx->meta_num] = meta; + meta->key = key; + meta->value = (char *)&meta[1]; + meta->size = size; + p_ctx->meta_num++; + + return meta; +} + +/*****************************************************************************/ +static VC_CONTAINER_METADATA_T *id3_read_metadata_entry( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_METADATA_KEY_T key, unsigned int len ) +{ + VC_CONTAINER_METADATA_T *meta; + + if ((meta = id3_metadata_append(p_ctx, key, len + 1)) != NULL) + { + unsigned int size = meta->size - 1; + READ_BYTES(p_ctx, meta->value, size); + + if (len > size) + { + LOG_DEBUG(p_ctx, "metadata value truncated (%d characters lost)", len - size); + SKIP_BYTES(p_ctx, len - size); + } + } + else + { + SKIP_BYTES(p_ctx, len); + } + + return meta; +} + +/*****************************************************************************/ +static VC_CONTAINER_METADATA_T *id3_read_metadata_entry_ex( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_METADATA_KEY_T key, unsigned int len, const char *encoding ) +{ + VC_CONTAINER_METADATA_T *meta; + + if ((meta = id3_metadata_append(p_ctx, key, encoding ? len + 2 : len + 1)) != NULL) + { + unsigned int size; + + if (encoding) + { + size = meta->size - 2; + READ_STRING_UTF16(p_ctx, meta->value, size, "ID3v2 data"); + } + else + { + size = meta->size - 1; + READ_STRING(p_ctx, meta->value, size, "ID3v2 data"); + } + + if (len > size) + { + LOG_DEBUG(p_ctx, "metadata value truncated (%d characters lost)", len - size); + SKIP_BYTES(p_ctx, len - size); + } + } + + return meta; +} + +/*****************************************************************************/ +static VC_CONTAINER_METADATA_T *id3_add_metadata_entry( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_METADATA_KEY_T key, const char *value ) +{ + VC_CONTAINER_METADATA_T *meta; + unsigned int len = strlen(value); + + if ((meta = id3_metadata_append(p_ctx, key, len + 1)) != NULL) + { + unsigned int size = meta->size - 1; + + if (len > size) + { + LOG_DEBUG(p_ctx, "metadata value truncated (%d characters lost)", len - size); + } + + strncpy(meta->value, value, size); + } + + return meta; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T id3_read_id3v2_frame( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_FOURCC_T frame_id, uint32_t frame_size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_METADATA_KEY_T key; + VC_CONTAINER_METADATA_T *meta = NULL; + uint8_t encoding; + const char *charset = NULL; + + if(frame_size < 1) return VC_CONTAINER_ERROR_CORRUPTED; + + switch (frame_id) + { + case VC_FOURCC('T','A','L','B'): key = VC_CONTAINER_METADATA_KEY_ALBUM; break; + case VC_FOURCC('T','I','T','2'): key = VC_CONTAINER_METADATA_KEY_TITLE; break; + case VC_FOURCC('T','R','C','K'): key = VC_CONTAINER_METADATA_KEY_TRACK; break; + case VC_FOURCC('T','P','E','1'): key = VC_CONTAINER_METADATA_KEY_ARTIST; break; + case VC_FOURCC('T','C','O','N'): key = VC_CONTAINER_METADATA_KEY_GENRE; break; + default: key = VC_CONTAINER_METADATA_KEY_UNKNOWN; break; + } + + if (key == VC_CONTAINER_METADATA_KEY_UNKNOWN) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + encoding = READ_U8(p_ctx, "ID3v2 text encoding byte"); + frame_size -= 1; + + switch(encoding) + { + case 0: /* ISO-8859-1 */ + case 3: /* UTF-8 */ + break; + case 1: /* UTF-16 with BOM */ + if(frame_size < 2) return VC_CONTAINER_ERROR_CORRUPTED; + SKIP_U16(p_ctx, "ID3v2 text encoding BOM"); /* FIXME: Check BOM, 0xFFFE vs 0xFEFFF */ + frame_size -= 2; + charset = "UTF16-LE"; + break; + case 2: /* UTF-16BE */ + charset = "UTF16-BE"; + break; + default: + LOG_DEBUG(p_ctx, "skipping frame, text encoding %x not supported", encoding); + SKIP_BYTES(p_ctx, frame_size); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + if ((meta = id3_read_metadata_entry_ex(p_ctx, key, frame_size, charset)) != NULL) + { + if (charset) + { + utf8_from_charset(charset, meta->value, meta->size, meta->value, meta->size); + } + + meta->encoding = VC_CONTAINER_CHAR_ENCODING_UTF8; /* Okay for ISO-8859-1 as well? */ + + status = VC_CONTAINER_SUCCESS; + } + else + { + SKIP_BYTES(p_ctx, frame_size); + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T id3_read_id3v2_tag( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint8_t maj_version, flags; + uint32_t tag_size, size = 0; + uint8_t peek_buf[10]; + + SKIP_STRING(p_ctx, 3, "ID3v2 identifier"); + maj_version = READ_U8(p_ctx, "ID3v2 version (major)"); + SKIP_U8(p_ctx, "ID3v2 version (minor)"); + flags = READ_U8(p_ctx, "ID3v2 flags"); + tag_size = READ_U32(p_ctx, "ID3v2 syncsafe tag size"); + tag_size = ID3_SYNC_SAFE(tag_size); + LOG_DEBUG(p_ctx, "ID3v2 tag size: %d", tag_size); + + /* Check that we support this major version */ + if (!(maj_version == 4 || maj_version == 3 || maj_version == 2)) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* We can't currently handle unsynchronisation */ + if ((flags >> 7) & 1) + { + LOG_DEBUG(p_ctx, "skipping unsynchronised tag, not supported"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + /* FIXME: check for version 2.2 and extract iTunes gapless playback information */ + if (maj_version == 2) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if ((flags >> 6) & 1) + { + /* Skip extended header, we don't support it */ + uint32_t ext_hdr_size; + LOG_DEBUG(p_ctx, "skipping ID3v2 extended header, not supported"); + ext_hdr_size = READ_U32(p_ctx, "ID3v2 syncsafe extended header size"); + ext_hdr_size = ID3_SYNC_SAFE(ext_hdr_size); + LOG_DEBUG(p_ctx, "ID3v2 extended header size: %d", ext_hdr_size); + SKIP_BYTES(p_ctx, MIN(tag_size, ext_hdr_size)); + size += ext_hdr_size; + } + + while (PEEK_BYTES(p_ctx, peek_buf, 10) == 10 && size < tag_size) + { + VC_CONTAINER_FOURCC_T frame_id; + uint32_t frame_size; + uint8_t format_flags; + + frame_id = READ_FOURCC(p_ctx, "Frame ID"); + frame_size = READ_U32(p_ctx, "Frame Size"); + + if (maj_version >= 4) + { + frame_size = ID3_SYNC_SAFE(frame_size); + LOG_DEBUG(p_ctx, "ID3v2 actual frame size: %d", frame_size); + } + + SKIP_U8(p_ctx, "ID3v2 status message flags"); + format_flags = READ_U8(p_ctx, "ID3v2 format description flags"); + + size += 10; + + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS || !frame_id) + break; + + /* Early exit if we detect an invalid tag size */ + if (size + frame_size > tag_size) + { + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + break; + } + + /* We can't currently handle unsynchronised frames */ + if ((format_flags >> 1) & 1) + { + LOG_DEBUG(p_ctx, "skipping unsynchronised frame, not supported"); + SKIP_BYTES(p_ctx, frame_size); + continue; + } + + if ((status = id3_read_id3v2_frame(p_ctx, frame_id, frame_size)) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "skipping unsupported frame"); + SKIP_BYTES(p_ctx, frame_size); + } + + size += frame_size; + } + + /* Try to skip to end of tag in case we bailed out early */ + if (size < tag_size) SKIP_BYTES(p_ctx, tag_size - size); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T id3_read_id3v1_tag( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint8_t track, genre; + char track_num[4] = {0}; + + SKIP_STRING(p_ctx, 3, "ID3v1 identifier"); + /* ID3v1 title */ + id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_TITLE, 30); + /* ID3v1 artist */ + id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_ARTIST, 30); + /* ID3v1 album */ + id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_ALBUM, 30); + /* ID3v1 year */ + id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_YEAR, 4); + SKIP_STRING(p_ctx, 28, "ID3v1 comment"); + if (READ_U8(p_ctx, "ID3v1 zero-byte") == 0) + { + track = READ_U8(p_ctx, "ID3v1 track"); + snprintf(track_num, sizeof(track_num) - 1, "%02d", track); + id3_add_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_TRACK, track_num); + } + else + { + SKIP_BYTES(p_ctx, 1); + } + genre = READ_U8(p_ctx, "ID3v1 genre"); + if (genre < countof(id3_genres)) + { + id3_add_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_GENRE, id3_genres[genre]); + } + + status = STREAM_STATUS(p_ctx); + + return status; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T id3_metadata_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T id3_metadata_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + uint8_t peek_buf[10]; + int64_t data_offset; + + if (PEEK_BYTES(p_ctx, peek_buf, 10) != 10) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Initial ID3v2 tag(s), variable size */ + while ((peek_buf[0] == 'I') && (peek_buf[1] == 'D') && (peek_buf[2] == '3')) + { + if ((status = id3_read_id3v2_tag(p_ctx)) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "error reading ID3v2 tag (%i)", status); + } + + if (PEEK_BYTES(p_ctx, peek_buf, 10) != 10) break; + } + + data_offset = STREAM_POSITION(p_ctx); + + /* ID3v1 tag, 128 bytes at the end of a file */ + if (p_ctx->priv->io->size >= INT64_C(128) && STREAM_SEEKABLE(p_ctx)) + { + SEEK(p_ctx, p_ctx->priv->io->size - INT64_C(128)); + if (PEEK_BYTES(p_ctx, peek_buf, 3) != 3) goto end; + + if ((peek_buf[0] == 'T') && (peek_buf[1] == 'A') && (peek_buf[2] == 'G')) + { + if ((status = id3_read_id3v1_tag(p_ctx)) != VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "error reading ID3v1 tag (%i)", status); + } + } + } + +end: + /* Restore position to start of data */ + if (STREAM_POSITION(p_ctx) != data_offset) + SEEK(p_ctx, data_offset); + + p_ctx->priv->pf_close = id3_metadata_reader_close; + + if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) goto error; + + return VC_CONTAINER_SUCCESS; + +error: + LOG_DEBUG(p_ctx, "error opening stream (%i)", status); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open id3_metadata_reader_open +#endif diff --git a/containers/metadata/id3/id3_metadata_strings.h b/containers/metadata/id3/id3_metadata_strings.h new file mode 100644 index 0000000..1c953d9 --- /dev/null +++ b/containers/metadata/id3/id3_metadata_strings.h @@ -0,0 +1,179 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* ID3 genre byte translation table */ +static const char* id3_genres[] = +{ + "Blues", + "Classic Rock", + "Country", + "Dance", + "Disco", + "Funk", + "Grunge", + "Hip-Hop", + "Jazz", + "Metal", + "New Age", + "Oldies", + "Other", + "Pop", + "R&B", + "Rap", + "Reggae", + "Rock", + "Techno", + "Industrial", + "Alternative", + "Ska", + "Death Metal", + "Pranks", + "Soundtrack", + "Euro-Techno", + "Ambient", + "Trip-Hop", + "Vocal", + "Jazz+Funk", + "Fusion", + "Trance", + "Classical", + "Instrumental", + "Acid", + "House", + "Game", + "Sound Clip", + "Gospel", + "Noise", + "Alternative Rock", + "Bass", + "Soul", + "Punk", + "Space", + "Meditative", + "Instrumental Pop", + "Instrumental Rock", + "Ethnic", + "Gothic", + "Darkwave", + "Techno-Industrial", + "Electronic", + "Pop-Folk", + "Eurodance", + "Dream", + "Southern Rock", + "Comedy", + "Cult", + "Gangsta", + "Top 40", + "Christian Rap", + "Pop/Funk", + "Jungle", + "Native American", + "Cabaret", + "New Wave", + "Psychadelic", + "Rave", + "Showtunes", + "Trailer", + "Lo-Fi", + "Tribal", + "Acid Punk", + "Acid Jazz", + "Polka", + "Retro", + "Musical", + "Rock & Roll", + "Hard Rock", + "Folk", + "Folk-Rock", + "National Folk", + "Swing", + "Fast Fusion", + "Bebob", + "Latin", + "Revival", + "Celtic", + "Bluegrass", + "Avantgarde", + "Gothic Rock", + "Progressive Rock", + "Psychedelic Rock", + "Symphonic Rock", + "Slow Rock", + "Big Band", + "Chorus", + "Easy Listening", + "Acoustic", + "Humour", + "Speech", + "Chanson", + "Opera", + "Chamber Music", + "Sonata", + "Symphony", + "Booty Bass", + "Primus", + "Porn Groove", + "Satire", + "Slow Jam", + "Club", + "Tango", + "Samba", + "Folklore", + "Ballad", + "Power Ballad", + "Rhythmic Soul", + "Freestyle", + "Duet", + "Punk Rock", + "Drum Solo", + "A capella", + "Euro-House", + "Dance Hall", + "Goa", + "Drum & Bass", + "Club-House", + "Hardcore", + "Terror", + "Indie", + "BritPop", + "Negerpunk", + "Polsk Punk", + "Beat", + "Christian Gangsta Rap", + "Heavy Metal", + "Black Metal", + "Crossover", + "Contemporary Christian", + "Christian Rock", + "Merengue", + "Salsa", + "Thrash Metal", + "Anime", + "JPop", + "SynthPop" +}; diff --git a/containers/mkv/CMakeLists.txt b/containers/mkv/CMakeLists.txt new file mode 100644 index 0000000..0c31a30 --- /dev/null +++ b/containers/mkv/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_mkv ${LIBRARY_TYPE} matroska_reader.c) + +target_link_libraries(reader_mkv containers) + +install(TARGETS reader_mkv DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/mkv/matroska_reader.c b/containers/mkv/matroska_reader.c new file mode 100644 index 0000000..8625d0c --- /dev/null +++ b/containers/mkv/matroska_reader.c @@ -0,0 +1,2323 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +//#define ENABLE_MKV_EXTRA_LOGGING +#define CONTAINER_IS_BIG_ENDIAN +#define CONTAINER_HELPER_LOG_INDENT(a) (a)->priv->module->element_level +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define MKV_TRACKS_MAX 16 +#define MKV_CODECID_MAX 32 +#define MKV_MAX_LACING_NUM 64 + +#define MKV_MAX_ENCODINGS 1 +#define MKV_MAX_ENCODING_DATA 256 + +#define MKV_MAX_ELEMENT_LEVEL 8 +#define MKV_MAX_CONSECUTIVE_UNKNOWN_ELEMENTS 5 +#define MKV_MAX_ELEMENT_SIZE (1<<29) /* Does not apply to the data element */ +#define MKV_MAX_STRING_SIZE 256 +#define MKV_ELEMENT_MIN_HEADER_SIZE 2 + +#define MKV_MAX_READER_STATE_LEVEL 4 + +#define MKV_SKIP_U8(ctx,n) (size -= 1, SKIP_U8(ctx,n)) +#define MKV_SKIP_U16(ctx,n) (size -= 2, SKIP_U16(ctx,n)) +#define MKV_SKIP_U24(ctx,n) (size -= 3, SKIP_U24(ctx,n)) +#define MKV_SKIP_U32(ctx,n) (size -= 4, SKIP_U32(ctx,n)) +#define MKV_SKIP_U64(ctx,n) (size -= 8, SKIP_U64(ctx,n)) +#define MKV_READ_U8(ctx,n) (size -= 1, READ_U8(ctx,n)) +#define MKV_READ_U16(ctx,n) (size -= 2, READ_U16(ctx,n)) +#define MKV_READ_U24(ctx,n) (size -= 3, READ_U24(ctx,n)) +#define MKV_READ_U32(ctx,n) (size -= 4, READ_U32(ctx,n)) +#define MKV_READ_U64(ctx,n) (size -= 8, READ_U64(ctx,n)) +#define MKV_READ_BYTES(ctx,buffer,sz) (size -= sz, READ_BYTES(ctx,buffer,sz)) +#define MKV_SKIP_BYTES(ctx,sz) (size -= sz, SKIP_BYTES(ctx,sz)) + +#define CHECK_POINT(a) do { \ + /*if(size < 0 && size != INT64_C(-1)) return VC_CONTAINER_ERROR_CORRUPTED;*/ \ + if(STREAM_STATUS(p_ctx)) return STREAM_STATUS(p_ctx); } while(0) + +static uint32_t mkv_io_read_id(VC_CONTAINER_IO_T *io, int64_t *size) +{ + uint32_t value, mask; + + value = vc_container_io_read_uint8(io); (*size)--; + for(mask = 0x80; mask; mask <<= 7) + { + if(value & mask) return value; + value = (value << 8) | vc_container_io_read_uint8(io); (*size)--; + } + return 0; +} + +static int64_t mkv_io_read_uint(VC_CONTAINER_IO_T *io, int64_t *size) +{ + uint64_t value, mask; + + value = vc_container_io_read_uint8(io); (*size)--; + if(value == 0xFF) return -1; + + for(mask = 0x80; mask; mask <<= 7) + { + if(value & mask) return value & ~mask; + value = (value << 8) | vc_container_io_read_uint8(io); (*size)--; + } + return 0; +} + +static int64_t mkv_io_read_sint(VC_CONTAINER_IO_T *io, int64_t *size) +{ + int64_t value, count = io->offset; + value = mkv_io_read_uint(io, size); + count = io->offset - count; + + switch(count) + { + case 1: value -= 0x3F; break; + case 2: value -= 0x1FFF; break; + case 3: value -= 0xFFFFF; break; + case 4: value -= 0x7FFFFFF; break; + default: break; + } + return value; +} + +#define MKV_READ_ID(ctx, n) mkv_io_read_id((ctx)->priv->io, &size) +#define MKV_READ_UINT(ctx, n) mkv_io_read_uint((ctx)->priv->io, &size) +#define MKV_READ_SINT(ctx, n) mkv_io_read_sint((ctx)->priv->io, &size) + +/****************************************************************************** +Type definitions. +******************************************************************************/ + +typedef enum +{ + MKV_ELEMENT_ID_UNKNOWN = 0, + + /* EBML Basics */ + MKV_ELEMENT_ID_EBML = 0x1A45DFA3, + MKV_ELEMENT_ID_EBML_VERSION = 0x4286, + MKV_ELEMENT_ID_EBML_READ_VERSION = 0x42F7, + MKV_ELEMENT_ID_EBML_MAX_ID_LENGTH = 0x42F2, + MKV_ELEMENT_ID_EBML_MAX_SIZE_LENGTH = 0x42F3, + MKV_ELEMENT_ID_DOCTYPE = 0x4282, + MKV_ELEMENT_ID_DOCTYPE_VERSION = 0x4287, + MKV_ELEMENT_ID_DOCTYPE_READ_VERSION = 0x4285, + + /* Global Elements */ + MKV_ELEMENT_ID_CRC32 = 0xBF, + MKV_ELEMENT_ID_VOID = 0xEC, + + /* Segment */ + MKV_ELEMENT_ID_SEGMENT = 0x18538067, + + /* Meta Seek Information */ + MKV_ELEMENT_ID_SEEK_HEAD = 0x114D9B74, + MKV_ELEMENT_ID_SEEK = 0x4DBB, + MKV_ELEMENT_ID_SEEK_ID = 0x53AB, + MKV_ELEMENT_ID_SEEK_POSITION = 0x53AC, + + /* Segment Information */ + MKV_ELEMENT_ID_INFO = 0x1549A966, + MKV_ELEMENT_ID_SEGMENT_UID = 0x73A4, + MKV_ELEMENT_ID_SEGMENT_FILENAME = 0x7384, + MKV_ELEMENT_ID_PREV_UID = 0x3CB923, + MKV_ELEMENT_ID_PREV_FILENAME = 0x3C83AB, + MKV_ELEMENT_ID_NEXT_UID = 0x3EB923, + MKV_ELEMENT_ID_NEXT_FILENAME = 0x3E83BB, + MKV_ELEMENT_ID_SEGMENT_FAMILY = 0x4444, + MKV_ELEMENT_ID_CHAPTER_TRANSLATE = 0x6924, + MKV_ELEMENT_ID_CHAPTER_TRANSLATE_EDITION_UID = 0x69FC, + MKV_ELEMENT_ID_CHAPTER_TRANSLATE_CODEC = 0x69BF, + MKV_ELEMENT_ID_CHAPTER_TRANSLATE_ID = 0x69A5, + MKV_ELEMENT_ID_TIMECODE_SCALE = 0x2AD7B1, + MKV_ELEMENT_ID_DURATION = 0x4489, + MKV_ELEMENT_ID_DATE_UTC = 0x4461, + MKV_ELEMENT_ID_TITLE = 0x7BA9, + MKV_ELEMENT_ID_MUXING_APP = 0x4D80, + MKV_ELEMENT_ID_WRITING_APP = 0x5741, + + /* Cluster */ + MKV_ELEMENT_ID_CLUSTER = 0x1F43B675, + MKV_ELEMENT_ID_TIMECODE = 0xE7, + MKV_ELEMENT_ID_SILENT_TRACKS = 0x5854, + MKV_ELEMENT_ID_SILENT_TRACK_NUMBER = 0x58D7, + MKV_ELEMENT_ID_POSITION = 0xA7, + MKV_ELEMENT_ID_PREV_SIZE = 0xAB, + MKV_ELEMENT_ID_BLOCKGROUP = 0xA0, + MKV_ELEMENT_ID_BLOCK = 0xA1, + MKV_ELEMENT_ID_BLOCK_ADDITIONS = 0x75A1, + MKV_ELEMENT_ID_BLOCK_MORE = 0xA6, + MKV_ELEMENT_ID_BLOCK_ADD_ID = 0xEE, + MKV_ELEMENT_ID_BLOCK_ADDITIONAL = 0xA5, + MKV_ELEMENT_ID_BLOCK_DURATION = 0x9B, + MKV_ELEMENT_ID_REFERENCE_PRIORITY = 0xFA, + MKV_ELEMENT_ID_REFERENCE_BLOCK = 0xFB, + MKV_ELEMENT_ID_CODEC_STATE = 0xA4, + MKV_ELEMENT_ID_SLICES = 0x8E, + MKV_ELEMENT_ID_TIME_SLICE = 0xE8, + MKV_ELEMENT_ID_LACE_NUMBER = 0xCC, + MKV_ELEMENT_ID_SIMPLE_BLOCK = 0xA3, + + /* Track */ + MKV_ELEMENT_ID_TRACKS = 0x1654AE6B, + MKV_ELEMENT_ID_TRACK_ENTRY = 0xAE, + MKV_ELEMENT_ID_TRACK_NUMBER = 0xD7, + MKV_ELEMENT_ID_TRACK_UID = 0x73C5, + MKV_ELEMENT_ID_TRACK_TYPE = 0x83, + MKV_ELEMENT_ID_FLAG_ENABLED = 0xB9, + MKV_ELEMENT_ID_FLAG_DEFAULT = 0x88, + MKV_ELEMENT_ID_FLAG_FORCED = 0x55AA, + MKV_ELEMENT_ID_FLAG_LACING = 0x9C, + MKV_ELEMENT_ID_MIN_CACHE = 0x6DE7, + MKV_ELEMENT_ID_MAX_CACHE = 0x6DF8, + MKV_ELEMENT_ID_DEFAULT_DURATION = 0x23E383, + MKV_ELEMENT_ID_TRACK_TIMECODE_SCALE = 0x23314F, + MKV_ELEMENT_ID_MAX_BLOCK_ADDITION_ID = 0x55EE, + MKV_ELEMENT_ID_NAME = 0x536E, + MKV_ELEMENT_ID_LANGUAGE = 0x22B59C, + MKV_ELEMENT_ID_TRACK_CODEC_ID = 0x86, + MKV_ELEMENT_ID_TRACK_CODEC_PRIVATE = 0x63A2, + MKV_ELEMENT_ID_TRACK_CODEC_NAME = 0x258688, + MKV_ELEMENT_ID_ATTACHMENT_LINK = 0x7446, + MKV_ELEMENT_ID_CODEC_DECODE_ALL = 0xAA, + MKV_ELEMENT_ID_TRACK_OVERLAY = 0x6FAB, + MKV_ELEMENT_ID_TRACK_TRANSLATE = 0x6624, + MKV_ELEMENT_ID_TRACK_TRANSLATE_EDITION_UID = 0x66FC, + MKV_ELEMENT_ID_TRACK_TRANSLATE_CODEC = 0x66BF, + MKV_ELEMENT_ID_TRACK_TRANSLATE_TRACK_ID = 0x66A5, + + /* Video */ + MKV_ELEMENT_ID_VIDEO = 0xE0, + MKV_ELEMENT_ID_FLAG_INTERLACED = 0x9A, + MKV_ELEMENT_ID_STEREO_MODE = 0x53B8, + MKV_ELEMENT_ID_PIXEL_WIDTH = 0xB0, + MKV_ELEMENT_ID_PIXEL_HEIGHT = 0xBA, + MKV_ELEMENT_ID_PIXEL_CROP_BOTTOM = 0x54AA, + MKV_ELEMENT_ID_PIXEL_CROP_TOP = 0x54BB, + MKV_ELEMENT_ID_PIXEL_CROP_LEFT = 0x54CC, + MKV_ELEMENT_ID_PIXEL_CROP_RIGHT = 0x54DD, + MKV_ELEMENT_ID_DISPLAY_WIDTH = 0x54B0, + MKV_ELEMENT_ID_DISPLAY_HEIGHT = 0x54BA, + MKV_ELEMENT_ID_DISPLAY_UNIT = 0x54B2, + MKV_ELEMENT_ID_ASPECT_RATIO_TYPE = 0x54B3, + MKV_ELEMENT_ID_COLOUR_SPACE = 0x2EB524, + MKV_ELEMENT_ID_FRAME_RATE = 0x2383E3, + + /* Audio */ + MKV_ELEMENT_ID_AUDIO = 0xE1, + MKV_ELEMENT_ID_SAMPLING_FREQUENCY = 0xB5, + MKV_ELEMENT_ID_OUTPUT_SAMPLING_FREQUENCY = 0x78B5, + MKV_ELEMENT_ID_CHANNELS = 0x9F, + MKV_ELEMENT_ID_BIT_DEPTH = 0x6264, + + /* Content Encoding */ + MKV_ELEMENT_ID_CONTENT_ENCODINGS = 0x6D80, + MKV_ELEMENT_ID_CONTENT_ENCODING = 0x6240, + MKV_ELEMENT_ID_CONTENT_ENCODING_ORDER = 0x5031, + MKV_ELEMENT_ID_CONTENT_ENCODING_SCOPE = 0x5032, + MKV_ELEMENT_ID_CONTENT_ENCODING_TYPE = 0x5033, + MKV_ELEMENT_ID_CONTENT_COMPRESSION = 0x5034, + MKV_ELEMENT_ID_CONTENT_COMPRESSION_ALGO = 0x4254, + MKV_ELEMENT_ID_CONTENT_COMPRESSION_SETTINGS = 0x4255, + MKV_ELEMENT_ID_CONTENT_ENCRYPTION = 0x5035, + MKV_ELEMENT_ID_CONTENT_ENCRYPTION_ALGO = 0x47E1, + MKV_ELEMENT_ID_CONTENT_ENCRYPTION_KEY_ID = 0x47E2, + MKV_ELEMENT_ID_CONTENT_SIGNATURE = 0x47E3, + MKV_ELEMENT_ID_CONTENT_SIGNATURE_KEY_ID = 0x47E4, + MKV_ELEMENT_ID_CONTENT_SIGNATURE_ALGO = 0x47E5, + MKV_ELEMENT_ID_CONTENT_SIGNATURE_HASH_ALGO = 0x47E6, + + /* Cueing Data */ + MKV_ELEMENT_ID_CUES = 0x1C53BB6B, + MKV_ELEMENT_ID_CUE_POINT = 0xBB, + MKV_ELEMENT_ID_CUE_TIME = 0xB3, + MKV_ELEMENT_ID_CUE_TRACK_POSITIONS = 0xB7, + MKV_ELEMENT_ID_CUE_TRACK = 0xF7, + MKV_ELEMENT_ID_CUE_CLUSTER_POSITION = 0xF1, + MKV_ELEMENT_ID_CUE_BLOCK_NUMBER = 0x5378, + + /* Attachments */ + MKV_ELEMENT_ID_ATTACHMENTS = 0x1941A469, + + /* Chapters */ + MKV_ELEMENT_ID_CHAPTERS = 0x1043A770, + + /* Tagging */ + MKV_ELEMENT_ID_TAGS = 0x1254C367, + MKV_ELEMENT_ID_TAG = 0x7373, + MKV_ELEMENT_ID_TAG_TARGETS = 0x63C0, + MKV_ELEMENT_ID_TAG_TARGET_TYPE_VALUE = 0x68CA, + MKV_ELEMENT_ID_TAG_TARGET_TYPE = 0x63CA, + MKV_ELEMENT_ID_TAG_TRACK_UID = 0x63C5, + MKV_ELEMENT_ID_TAG_EDITION_UID = 0x63C9, + MKV_ELEMENT_ID_TAG_CHAPTER_UID = 0x63C4, + MKV_ELEMENT_ID_TAG_ATTACHMENT_UID = 0x63C6, + MKV_ELEMENT_ID_TAG_SIMPLE_TAG = 0x67C8, + MKV_ELEMENT_ID_TAG_NAME = 0x45A3, + MKV_ELEMENT_ID_TAG_LANGUAGE = 0x447A, + MKV_ELEMENT_ID_TAG_DEFAULT = 0x4484, + MKV_ELEMENT_ID_TAG_STRING = 0x4487, + MKV_ELEMENT_ID_TAG_BINARY = 0x4485, + + MKV_ELEMENT_ID_INVALID = 0xFFFFFFFF +} MKV_ELEMENT_ID_T; + +/** Context for our reader + */ + +typedef struct +{ + unsigned int track; + unsigned int flags; + int64_t pts; + int64_t cluster_timecode; + int64_t prev_cluster_size; /* Size of the previous cluster if available */ + int64_t frame_duration; + + int level; + struct { + int64_t offset; + int64_t data_start; + int64_t data_offset; + int64_t size; + MKV_ELEMENT_ID_T id; + } levels[MKV_MAX_READER_STATE_LEVEL]; + + bool eos; + bool corrupted; + bool seen_ref_block; + + uint32_t lacing_num_frames; + uint32_t lacing_size; + uint16_t lacing_sizes[MKV_MAX_LACING_NUM]; + uint32_t lacing_current_size; + + /* For header stripping compression */ + uint32_t header_size; + uint8_t *header_data; + uint32_t header_size_backup; +} MKV_READER_STATE_T; + +typedef struct +{ + const MKV_ELEMENT_ID_T id; + const MKV_ELEMENT_ID_T parent_id; + const char *psz_name; + VC_CONTAINER_STATUS_T (*pf_func)(VC_CONTAINER_T *, MKV_ELEMENT_ID_T, int64_t); + +} MKV_ELEMENT_T; + +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + MKV_READER_STATE_T *state; + MKV_READER_STATE_T track_state; + + /* Information extracted from the track entry */ + uint32_t number; + uint32_t type; + int64_t timecode_scale; + uint32_t duration; + int64_t frame_duration; + char codecid[MKV_CODECID_MAX]; + + union { + /* video specific */ + struct { + unsigned int interlaced:1; + unsigned int stereo_mode:2; + uint32_t pixel_width; + uint32_t pixel_height; + uint32_t pixel_crop_bottom; + uint32_t pixel_crop_top; + uint32_t pixel_crop_left; + uint32_t pixel_crop_right; + uint32_t display_width; + uint32_t display_height; + uint32_t display_unit; + uint32_t aspect_ratio_type; + float frame_rate; + } video; + + /* audio specific */ + struct { + uint32_t sampling_frequency; + uint32_t output_sampling_frequency; + uint32_t channels; + uint32_t bit_depth; + } audio; + } es_type; + + /* content encoding (i.e. lossless compression and encryption) */ + unsigned int encodings_num; + struct { + enum { + MKV_CONTENT_ENCODING_COMPRESSION_ZLIB, + MKV_CONTENT_ENCODING_COMPRESSION_HEADER, + MKV_CONTENT_ENCODING_ENCRYPTION, + MKV_CONTENT_ENCODING_UNKNOWN + } type; + unsigned int data_size; + uint8_t *data; + } encodings[MKV_MAX_ENCODINGS]; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + MKV_ELEMENT_T *elements_list; + int element_level; + MKV_ELEMENT_ID_T parent_id; + + uint64_t element_offset; /**< Offset to the start of the current element */ + + uint64_t segment_offset; /**< Offset to the start of the data packets */ + int64_t segment_size; + + int tracks_num; + VC_CONTAINER_TRACK_T *tracks[MKV_TRACKS_MAX]; + + MKV_READER_STATE_T state; + int64_t timecode_scale; + float duration; + + uint64_t cluster_offset; /**< Offset to the first cluster */ + uint64_t cues_offset; /**< Offset to the start of the seeking cues */ + uint64_t tags_offset; /**< Offset to the start of the tags */ + + /* + * Variables only used during parsing of the header + */ + + VC_CONTAINER_TRACK_T *parsing; /**< Current track being parsed */ + bool is_doctype_valid; + + MKV_ELEMENT_ID_T seekhead_elem_id; + int64_t seekhead_elem_offset; + + /* Cues */ + unsigned int cue_track; + int64_t cue_timecode; + uint64_t cue_cluster_offset; + unsigned int cue_block; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T mkv_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Prototypes for local functions +******************************************************************************/ +static VC_CONTAINER_STATUS_T mkv_read_element( VC_CONTAINER_T *p_ctx, int64_t size, MKV_ELEMENT_ID_T parent_id ); +static VC_CONTAINER_STATUS_T mkv_read_elements( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_element_data_uint( VC_CONTAINER_T *p_ctx, int64_t size, uint64_t *value ); +static VC_CONTAINER_STATUS_T mkv_read_element_data_float( VC_CONTAINER_T *p_ctx, int64_t size, double *value ); +static VC_CONTAINER_STATUS_T mkv_read_element_ebml( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_ebml( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_element_segment( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_element_track_entry( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_track_entry( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_video( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_audio( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_info( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_element_encoding( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_encoding( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_compression( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_seek_head( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_element_cues( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); +static VC_CONTAINER_STATUS_T mkv_read_subelements_cue_point( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); + +static VC_CONTAINER_STATUS_T mkv_read_subelements_cluster( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ); + +/****************************************************************************** +List of element IDs and their associated processing functions +******************************************************************************/ +MKV_ELEMENT_T mkv_elements_list[] = +{ + /* EBML Basics */ + {MKV_ELEMENT_ID_EBML, MKV_ELEMENT_ID_UNKNOWN, "EBML", mkv_read_element_ebml}, + {MKV_ELEMENT_ID_EBML_VERSION, MKV_ELEMENT_ID_EBML, "EBMLVersion", mkv_read_subelements_ebml}, + {MKV_ELEMENT_ID_EBML_READ_VERSION, MKV_ELEMENT_ID_EBML, "EBMLReadVersion", mkv_read_subelements_ebml}, + {MKV_ELEMENT_ID_EBML_MAX_ID_LENGTH, MKV_ELEMENT_ID_EBML, "EBMLMaxIDLength", mkv_read_subelements_ebml}, + {MKV_ELEMENT_ID_EBML_MAX_SIZE_LENGTH, MKV_ELEMENT_ID_EBML, "EBMLMaxSizeLength", mkv_read_subelements_ebml}, + {MKV_ELEMENT_ID_DOCTYPE, MKV_ELEMENT_ID_EBML, "DocType", mkv_read_subelements_ebml}, + {MKV_ELEMENT_ID_DOCTYPE_VERSION, MKV_ELEMENT_ID_EBML, "DocTypeVersion", mkv_read_subelements_ebml}, + {MKV_ELEMENT_ID_DOCTYPE_READ_VERSION, MKV_ELEMENT_ID_EBML, "DocTypeReadVersion", mkv_read_subelements_ebml}, + + /* Global Elements */ + {MKV_ELEMENT_ID_CRC32, MKV_ELEMENT_ID_INVALID, "CRC-32", 0}, + {MKV_ELEMENT_ID_VOID, MKV_ELEMENT_ID_INVALID, "Void", 0}, + + /* Segment */ + {MKV_ELEMENT_ID_SEGMENT, MKV_ELEMENT_ID_UNKNOWN, "Segment", mkv_read_element_segment}, + + /* Meta Seek Information */ + {MKV_ELEMENT_ID_SEEK_HEAD, MKV_ELEMENT_ID_SEGMENT, "SeekHead", mkv_read_elements}, + {MKV_ELEMENT_ID_SEEK, MKV_ELEMENT_ID_SEEK_HEAD, "Seek", mkv_read_subelements_seek_head}, + {MKV_ELEMENT_ID_SEEK_ID, MKV_ELEMENT_ID_SEEK, "SeekID", mkv_read_subelements_seek_head}, + {MKV_ELEMENT_ID_SEEK_POSITION, MKV_ELEMENT_ID_SEEK, "SeekPosition", mkv_read_subelements_seek_head}, + + /* Segment Information */ + {MKV_ELEMENT_ID_INFO, MKV_ELEMENT_ID_SEGMENT, "Info", mkv_read_elements}, + {MKV_ELEMENT_ID_SEGMENT_UID, MKV_ELEMENT_ID_INFO, "SegmentUID", 0}, + {MKV_ELEMENT_ID_SEGMENT_FILENAME, MKV_ELEMENT_ID_INFO, "SegmentFilename", 0}, + {MKV_ELEMENT_ID_PREV_UID, MKV_ELEMENT_ID_INFO, "PrevUID", 0}, + {MKV_ELEMENT_ID_PREV_FILENAME, MKV_ELEMENT_ID_INFO, "PrevFilename", 0}, + {MKV_ELEMENT_ID_NEXT_UID, MKV_ELEMENT_ID_INFO, "NextUID", 0}, + {MKV_ELEMENT_ID_NEXT_FILENAME, MKV_ELEMENT_ID_INFO, "NextFilename", 0}, + {MKV_ELEMENT_ID_SEGMENT_FAMILY, MKV_ELEMENT_ID_INFO, "SegmentFamily", 0}, + {MKV_ELEMENT_ID_CHAPTER_TRANSLATE, MKV_ELEMENT_ID_INFO, "ChapterTranslate", 0}, + {MKV_ELEMENT_ID_CHAPTER_TRANSLATE_EDITION_UID, MKV_ELEMENT_ID_INFO, "ChapterTranslateEditionUID", 0}, + {MKV_ELEMENT_ID_CHAPTER_TRANSLATE_CODEC, MKV_ELEMENT_ID_INFO, "ChapterTranslateCodec", 0}, + {MKV_ELEMENT_ID_CHAPTER_TRANSLATE_ID, MKV_ELEMENT_ID_INFO, "ChapterTranslateID", 0}, + {MKV_ELEMENT_ID_TIMECODE_SCALE, MKV_ELEMENT_ID_INFO, "TimecodeScale", mkv_read_subelements_info}, + {MKV_ELEMENT_ID_DURATION, MKV_ELEMENT_ID_INFO, "Duration", mkv_read_subelements_info}, + {MKV_ELEMENT_ID_DATE_UTC, MKV_ELEMENT_ID_INFO, "DateUTC", 0}, + {MKV_ELEMENT_ID_TITLE, MKV_ELEMENT_ID_INFO, "Title", mkv_read_subelements_info}, + {MKV_ELEMENT_ID_MUXING_APP, MKV_ELEMENT_ID_INFO, "MuxingApp", mkv_read_subelements_info}, + {MKV_ELEMENT_ID_WRITING_APP, MKV_ELEMENT_ID_INFO, "WritingApp", mkv_read_subelements_info}, + + /* Cluster */ + {MKV_ELEMENT_ID_CLUSTER, MKV_ELEMENT_ID_SEGMENT, "Cluster", 0}, + {MKV_ELEMENT_ID_TIMECODE, MKV_ELEMENT_ID_CLUSTER, "Timecode", 0}, + {MKV_ELEMENT_ID_SILENT_TRACKS, MKV_ELEMENT_ID_CLUSTER, "SilentTracks", 0}, + {MKV_ELEMENT_ID_SILENT_TRACK_NUMBER, MKV_ELEMENT_ID_CLUSTER, "SilentTrackNumber", 0}, + {MKV_ELEMENT_ID_POSITION, MKV_ELEMENT_ID_CLUSTER, "Position", 0}, + {MKV_ELEMENT_ID_PREV_SIZE, MKV_ELEMENT_ID_CLUSTER, "PrevSize", 0}, + {MKV_ELEMENT_ID_BLOCKGROUP, MKV_ELEMENT_ID_CLUSTER, "BlockGroup", 0}, + {MKV_ELEMENT_ID_BLOCK, MKV_ELEMENT_ID_BLOCKGROUP, "Block", 0}, + {MKV_ELEMENT_ID_BLOCK_ADDITIONS, MKV_ELEMENT_ID_BLOCKGROUP, "BlockAdditions", 0}, + {MKV_ELEMENT_ID_BLOCK_MORE, MKV_ELEMENT_ID_BLOCK_ADDITIONS, "BlockMore", 0}, + {MKV_ELEMENT_ID_BLOCK_ADD_ID, MKV_ELEMENT_ID_BLOCK_MORE, "BlockAddId", 0}, + {MKV_ELEMENT_ID_BLOCK_ADDITIONAL, MKV_ELEMENT_ID_BLOCK_MORE, "BlockAdditional", 0}, + {MKV_ELEMENT_ID_BLOCK_DURATION, MKV_ELEMENT_ID_BLOCKGROUP, "BlockDuration", 0}, + {MKV_ELEMENT_ID_REFERENCE_PRIORITY, MKV_ELEMENT_ID_BLOCKGROUP, "ReferencePriority", 0}, + {MKV_ELEMENT_ID_REFERENCE_BLOCK, MKV_ELEMENT_ID_BLOCKGROUP, "ReferenceBlock", 0}, + {MKV_ELEMENT_ID_CODEC_STATE, MKV_ELEMENT_ID_BLOCKGROUP, "CodecState", 0}, + {MKV_ELEMENT_ID_SLICES, MKV_ELEMENT_ID_BLOCKGROUP, "Slices", 0}, + {MKV_ELEMENT_ID_TIME_SLICE, MKV_ELEMENT_ID_SLICES, "TimeSlice", 0}, + {MKV_ELEMENT_ID_LACE_NUMBER, MKV_ELEMENT_ID_TIME_SLICE, "LaceNumber", 0}, + {MKV_ELEMENT_ID_SIMPLE_BLOCK, MKV_ELEMENT_ID_CLUSTER, "SimpleBlock", 0}, + + /* Track */ + {MKV_ELEMENT_ID_TRACKS, MKV_ELEMENT_ID_SEGMENT, "Tracks", mkv_read_elements}, + {MKV_ELEMENT_ID_TRACK_ENTRY, MKV_ELEMENT_ID_TRACKS, "TrackEntry", mkv_read_element_track_entry}, + {MKV_ELEMENT_ID_TRACK_NUMBER, MKV_ELEMENT_ID_TRACK_ENTRY, "TrackNumber", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_TRACK_UID, MKV_ELEMENT_ID_TRACK_ENTRY, "TrackUID", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_TRACK_TYPE, MKV_ELEMENT_ID_TRACK_ENTRY, "TrackType", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_FLAG_ENABLED, MKV_ELEMENT_ID_TRACK_ENTRY, "FlagEnabled", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_FLAG_DEFAULT, MKV_ELEMENT_ID_TRACK_ENTRY, "FlagDefault", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_FLAG_FORCED, MKV_ELEMENT_ID_TRACK_ENTRY, "FlagForced", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_FLAG_LACING, MKV_ELEMENT_ID_TRACK_ENTRY, "FlagLacing", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_MIN_CACHE, MKV_ELEMENT_ID_TRACK_ENTRY, "MinCache", 0}, + {MKV_ELEMENT_ID_MAX_CACHE, MKV_ELEMENT_ID_TRACK_ENTRY, "MaxCache", 0}, + {MKV_ELEMENT_ID_DEFAULT_DURATION, MKV_ELEMENT_ID_TRACK_ENTRY, "DefaultDuration", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_TRACK_TIMECODE_SCALE, MKV_ELEMENT_ID_TRACK_ENTRY, "TrackTimecodeScale", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_MAX_BLOCK_ADDITION_ID, MKV_ELEMENT_ID_TRACK_ENTRY, "MaxBlockAdditionID", 0}, + {MKV_ELEMENT_ID_NAME, MKV_ELEMENT_ID_TRACK_ENTRY, "Name", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_LANGUAGE, MKV_ELEMENT_ID_TRACK_ENTRY, "Language", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_TRACK_CODEC_ID, MKV_ELEMENT_ID_TRACK_ENTRY, "CodecID", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_TRACK_CODEC_PRIVATE, MKV_ELEMENT_ID_TRACK_ENTRY, "CodecPrivate", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_TRACK_CODEC_NAME, MKV_ELEMENT_ID_TRACK_ENTRY, "CodecName", mkv_read_subelements_track_entry}, + {MKV_ELEMENT_ID_ATTACHMENT_LINK, MKV_ELEMENT_ID_TRACK_ENTRY, "AttachmentLink", 0}, + {MKV_ELEMENT_ID_CODEC_DECODE_ALL, MKV_ELEMENT_ID_TRACK_ENTRY, "DecodeAll", 0}, + {MKV_ELEMENT_ID_TRACK_OVERLAY, MKV_ELEMENT_ID_TRACK_ENTRY, "TrackOverlay", 0}, + {MKV_ELEMENT_ID_TRACK_TRANSLATE, MKV_ELEMENT_ID_TRACK_ENTRY, "TrackTranslate", 0}, + {MKV_ELEMENT_ID_TRACK_TRANSLATE_EDITION_UID, MKV_ELEMENT_ID_TRACK_TRANSLATE, "TrackTranslateEditionUID", 0}, + {MKV_ELEMENT_ID_TRACK_TRANSLATE_CODEC, MKV_ELEMENT_ID_TRACK_TRANSLATE, "TrackTranslateCodec", 0}, + {MKV_ELEMENT_ID_TRACK_TRANSLATE_TRACK_ID, MKV_ELEMENT_ID_TRACK_TRANSLATE, "TrackTranslateTrackID", 0}, + + /* Video */ + {MKV_ELEMENT_ID_VIDEO, MKV_ELEMENT_ID_TRACK_ENTRY, "Video", mkv_read_elements}, + {MKV_ELEMENT_ID_FLAG_INTERLACED, MKV_ELEMENT_ID_VIDEO, "FlagInterlaced", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_STEREO_MODE, MKV_ELEMENT_ID_VIDEO, "StereoMode", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_PIXEL_WIDTH, MKV_ELEMENT_ID_VIDEO, "PixelWidth", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_PIXEL_HEIGHT, MKV_ELEMENT_ID_VIDEO, "PixelHeight", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_PIXEL_CROP_BOTTOM, MKV_ELEMENT_ID_VIDEO, "PixelCropBottom", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_PIXEL_CROP_TOP, MKV_ELEMENT_ID_VIDEO, "PixelCropTop", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_PIXEL_CROP_LEFT, MKV_ELEMENT_ID_VIDEO, "PixelCropLeft", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_PIXEL_CROP_RIGHT, MKV_ELEMENT_ID_VIDEO, "PixelCropRight", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_DISPLAY_WIDTH, MKV_ELEMENT_ID_VIDEO, "DisplayWidth", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_DISPLAY_HEIGHT, MKV_ELEMENT_ID_VIDEO, "DisplayHeight", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_DISPLAY_UNIT, MKV_ELEMENT_ID_VIDEO, "DisplayUnit", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_ASPECT_RATIO_TYPE, MKV_ELEMENT_ID_VIDEO, "AspectRatioType", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_COLOUR_SPACE, MKV_ELEMENT_ID_VIDEO, "ColourSpace", mkv_read_subelements_video}, + {MKV_ELEMENT_ID_FRAME_RATE, MKV_ELEMENT_ID_VIDEO, "FrameRate", mkv_read_subelements_video}, + + /* Audio */ + {MKV_ELEMENT_ID_AUDIO, MKV_ELEMENT_ID_TRACK_ENTRY, "Audio", mkv_read_elements}, + {MKV_ELEMENT_ID_SAMPLING_FREQUENCY, MKV_ELEMENT_ID_AUDIO, "SamplingFrequency", mkv_read_subelements_audio}, + {MKV_ELEMENT_ID_OUTPUT_SAMPLING_FREQUENCY, MKV_ELEMENT_ID_AUDIO, "OutputSamplingFrequency", mkv_read_subelements_audio}, + {MKV_ELEMENT_ID_CHANNELS, MKV_ELEMENT_ID_AUDIO, "Channels", mkv_read_subelements_audio}, + {MKV_ELEMENT_ID_BIT_DEPTH, MKV_ELEMENT_ID_AUDIO, "BitDepth", mkv_read_subelements_audio}, + + /* Content Encoding */ + {MKV_ELEMENT_ID_CONTENT_ENCODINGS, MKV_ELEMENT_ID_TRACK_ENTRY, "ContentEncodings", mkv_read_elements}, + {MKV_ELEMENT_ID_CONTENT_ENCODING, MKV_ELEMENT_ID_CONTENT_ENCODINGS, "ContentEncoding", mkv_read_element_encoding}, + {MKV_ELEMENT_ID_CONTENT_ENCODING_ORDER, MKV_ELEMENT_ID_CONTENT_ENCODING, "ContentEncodingOrder", mkv_read_subelements_encoding}, + {MKV_ELEMENT_ID_CONTENT_ENCODING_SCOPE, MKV_ELEMENT_ID_CONTENT_ENCODING, "ContentEncodingScope", mkv_read_subelements_encoding}, + {MKV_ELEMENT_ID_CONTENT_ENCODING_TYPE, MKV_ELEMENT_ID_CONTENT_ENCODING, "ContentEncodingType", mkv_read_subelements_encoding}, + {MKV_ELEMENT_ID_CONTENT_COMPRESSION, MKV_ELEMENT_ID_CONTENT_ENCODING, "ContentCompression", mkv_read_elements}, + {MKV_ELEMENT_ID_CONTENT_COMPRESSION_ALGO, MKV_ELEMENT_ID_CONTENT_COMPRESSION, "ContentCompAlgo", mkv_read_subelements_compression}, + {MKV_ELEMENT_ID_CONTENT_COMPRESSION_SETTINGS, MKV_ELEMENT_ID_CONTENT_COMPRESSION, "ContentCompSettings", mkv_read_subelements_compression}, + {MKV_ELEMENT_ID_CONTENT_ENCRYPTION, MKV_ELEMENT_ID_CONTENT_ENCODING, "ContentEncryption", mkv_read_elements}, + {MKV_ELEMENT_ID_CONTENT_ENCRYPTION_ALGO, MKV_ELEMENT_ID_CONTENT_ENCRYPTION, "ContentEncAlgo", 0}, + {MKV_ELEMENT_ID_CONTENT_ENCRYPTION_KEY_ID, MKV_ELEMENT_ID_CONTENT_ENCRYPTION, "ContentEncKeyID", 0}, + {MKV_ELEMENT_ID_CONTENT_SIGNATURE, MKV_ELEMENT_ID_CONTENT_ENCRYPTION, "ContentSignature", 0}, + {MKV_ELEMENT_ID_CONTENT_SIGNATURE_KEY_ID, MKV_ELEMENT_ID_CONTENT_ENCRYPTION, "ContentSigKeyID", 0}, + {MKV_ELEMENT_ID_CONTENT_SIGNATURE_ALGO, MKV_ELEMENT_ID_CONTENT_ENCRYPTION, "ContentSigAlgo", 0}, + {MKV_ELEMENT_ID_CONTENT_SIGNATURE_HASH_ALGO, MKV_ELEMENT_ID_CONTENT_ENCRYPTION, "ContentSigHashAlgo", 0}, + + /* Cueing data */ + {MKV_ELEMENT_ID_CUES, MKV_ELEMENT_ID_SEGMENT, "Cues", mkv_read_element_cues}, + {MKV_ELEMENT_ID_CUE_POINT, MKV_ELEMENT_ID_CUES, "Cue Point", mkv_read_elements}, + {MKV_ELEMENT_ID_CUE_TIME, MKV_ELEMENT_ID_CUE_POINT, "Cue Time", 0}, + {MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, MKV_ELEMENT_ID_CUE_POINT, "Cue Track Positions", mkv_read_elements}, + {MKV_ELEMENT_ID_CUE_TRACK, MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, "Cue Track", 0}, + {MKV_ELEMENT_ID_CUE_CLUSTER_POSITION, MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, "Cue Cluster Position", 0}, + {MKV_ELEMENT_ID_CUE_BLOCK_NUMBER, MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, "Cue Block Number", 0}, + + /* Attachments */ + {MKV_ELEMENT_ID_ATTACHMENTS, MKV_ELEMENT_ID_SEGMENT, "Attachments", 0}, + + /* Chapters */ + {MKV_ELEMENT_ID_CHAPTERS, MKV_ELEMENT_ID_SEGMENT, "Chapters", 0}, + + /* Tagging */ + {MKV_ELEMENT_ID_TAGS, MKV_ELEMENT_ID_SEGMENT, "Tags", mkv_read_elements}, + {MKV_ELEMENT_ID_TAG, MKV_ELEMENT_ID_TAGS, "Tag", mkv_read_elements}, + {MKV_ELEMENT_ID_TAG_TARGETS, MKV_ELEMENT_ID_TAG, "Tag Targets", mkv_read_elements}, + {MKV_ELEMENT_ID_TAG_TARGET_TYPE_VALUE, MKV_ELEMENT_ID_TAG_TARGETS, "Tag Target Type Value", 0}, + {MKV_ELEMENT_ID_TAG_TARGET_TYPE, MKV_ELEMENT_ID_TAG_TARGETS, "Tag Target Type", 0}, + {MKV_ELEMENT_ID_TAG_TRACK_UID, MKV_ELEMENT_ID_TAG_TARGETS, "Tag Track UID", 0}, + {MKV_ELEMENT_ID_TAG_EDITION_UID, MKV_ELEMENT_ID_TAG_TARGETS, "Tag Edition UID", 0}, + {MKV_ELEMENT_ID_TAG_CHAPTER_UID, MKV_ELEMENT_ID_TAG_TARGETS, "Tag Chapter UID", 0}, + {MKV_ELEMENT_ID_TAG_ATTACHMENT_UID, MKV_ELEMENT_ID_TAG_TARGETS, "Tag Attachment UID", 0}, + {MKV_ELEMENT_ID_TAG_SIMPLE_TAG, MKV_ELEMENT_ID_TAG, "Simple Tag", mkv_read_elements}, + {MKV_ELEMENT_ID_TAG_NAME, MKV_ELEMENT_ID_TAG_SIMPLE_TAG, "Tag Name", 0}, + {MKV_ELEMENT_ID_TAG_LANGUAGE, MKV_ELEMENT_ID_TAG_SIMPLE_TAG, "Tag Language", 0}, + {MKV_ELEMENT_ID_TAG_DEFAULT, MKV_ELEMENT_ID_TAG_SIMPLE_TAG, "Tag Default", 0}, + {MKV_ELEMENT_ID_TAG_STRING, MKV_ELEMENT_ID_TAG_SIMPLE_TAG, "Tag String", 0}, + {MKV_ELEMENT_ID_TAG_BINARY, MKV_ELEMENT_ID_TAG_SIMPLE_TAG, "Tag Binary", 0}, + + {MKV_ELEMENT_ID_UNKNOWN, MKV_ELEMENT_ID_INVALID, "unknown", 0} +}; + +MKV_ELEMENT_T mkv_cluster_elements_list[] = +{ + /* Cluster */ + {MKV_ELEMENT_ID_CLUSTER, MKV_ELEMENT_ID_SEGMENT, "Cluster", 0}, + {MKV_ELEMENT_ID_TIMECODE, MKV_ELEMENT_ID_CLUSTER, "Timecode", mkv_read_subelements_cluster}, + {MKV_ELEMENT_ID_SILENT_TRACKS, MKV_ELEMENT_ID_CLUSTER, "SilentTracks", 0}, + {MKV_ELEMENT_ID_SILENT_TRACK_NUMBER, MKV_ELEMENT_ID_CLUSTER, "SilentTrackNumber", 0}, + {MKV_ELEMENT_ID_POSITION, MKV_ELEMENT_ID_CLUSTER, "Position", 0}, + {MKV_ELEMENT_ID_PREV_SIZE, MKV_ELEMENT_ID_CLUSTER, "PrevSize", 0}, + {MKV_ELEMENT_ID_BLOCKGROUP, MKV_ELEMENT_ID_CLUSTER, "BlockGroup", 0}, + {MKV_ELEMENT_ID_BLOCK, MKV_ELEMENT_ID_BLOCKGROUP, "Block", 0}, + {MKV_ELEMENT_ID_BLOCK_ADDITIONS, MKV_ELEMENT_ID_BLOCKGROUP, "BlockAdditions", 0}, + {MKV_ELEMENT_ID_BLOCK_MORE, MKV_ELEMENT_ID_BLOCK_ADDITIONS, "BlockMore", 0}, + {MKV_ELEMENT_ID_BLOCK_ADD_ID, MKV_ELEMENT_ID_BLOCK_MORE, "BlockAddId", 0}, + {MKV_ELEMENT_ID_BLOCK_ADDITIONAL, MKV_ELEMENT_ID_BLOCK_MORE, "BlockAdditional", 0}, + {MKV_ELEMENT_ID_BLOCK_DURATION, MKV_ELEMENT_ID_BLOCKGROUP, "BlockDuration", mkv_read_subelements_cluster}, + {MKV_ELEMENT_ID_REFERENCE_PRIORITY, MKV_ELEMENT_ID_BLOCKGROUP, "ReferencePriority", 0}, + {MKV_ELEMENT_ID_REFERENCE_BLOCK, MKV_ELEMENT_ID_BLOCKGROUP, "ReferenceBlock", 0}, + {MKV_ELEMENT_ID_CODEC_STATE, MKV_ELEMENT_ID_BLOCKGROUP, "CodecState", 0}, + {MKV_ELEMENT_ID_SLICES, MKV_ELEMENT_ID_BLOCKGROUP, "Slices", 0}, + {MKV_ELEMENT_ID_TIME_SLICE, MKV_ELEMENT_ID_SLICES, "TimeSlice", 0}, + {MKV_ELEMENT_ID_LACE_NUMBER, MKV_ELEMENT_ID_TIME_SLICE, "LaceNumber", 0}, + {MKV_ELEMENT_ID_SIMPLE_BLOCK, MKV_ELEMENT_ID_CLUSTER, "SimpleBlock", 0}, + + /* Global Elements */ + {MKV_ELEMENT_ID_CRC32, MKV_ELEMENT_ID_INVALID, "CRC-32", 0}, + {MKV_ELEMENT_ID_VOID, MKV_ELEMENT_ID_INVALID, "Void", 0}, + + {MKV_ELEMENT_ID_UNKNOWN, MKV_ELEMENT_ID_INVALID, "unknown", 0} +}; + +MKV_ELEMENT_T mkv_cue_elements_list[] = +{ + /* Cueing data */ + {MKV_ELEMENT_ID_CUES, MKV_ELEMENT_ID_SEGMENT, "Cues", 0}, + {MKV_ELEMENT_ID_CUE_POINT, MKV_ELEMENT_ID_CUES, "Cue Point", mkv_read_elements}, + {MKV_ELEMENT_ID_CUE_TIME, MKV_ELEMENT_ID_CUE_POINT, "Cue Time", mkv_read_subelements_cue_point}, + {MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, MKV_ELEMENT_ID_CUE_POINT, "Cue Track Positions", mkv_read_elements}, + {MKV_ELEMENT_ID_CUE_TRACK, MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, "Cue Track", mkv_read_subelements_cue_point}, + {MKV_ELEMENT_ID_CUE_CLUSTER_POSITION, MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, "Cue Cluster Position", mkv_read_subelements_cue_point}, + {MKV_ELEMENT_ID_CUE_BLOCK_NUMBER, MKV_ELEMENT_ID_CUE_TRACK_POSITIONS, "Cue Block Number", mkv_read_subelements_cue_point}, + + /* Global Elements */ + {MKV_ELEMENT_ID_CRC32, MKV_ELEMENT_ID_INVALID, "CRC-32", 0}, + {MKV_ELEMENT_ID_VOID, MKV_ELEMENT_ID_INVALID, "Void", 0}, + + {MKV_ELEMENT_ID_UNKNOWN, MKV_ELEMENT_ID_INVALID, "unknown", 0} +}; + +/****************************************************************************** +List of codec mapping +******************************************************************************/ +static const struct { + VC_CONTAINER_FOURCC_T fourcc; + const char *codecid; + VC_CONTAINER_FOURCC_T variant; +} codecid_to_fourcc_table[] = +{ + /* Video */ + {VC_CONTAINER_CODEC_MP1V, "V_MPEG1", 0}, + {VC_CONTAINER_CODEC_MP2V, "V_MPEG2", 0}, + {VC_CONTAINER_CODEC_MP4V, "V_MPEG4/ISO/ASP", 0}, + {VC_CONTAINER_CODEC_MP4V, "V_MPEG4/ISO/SP", 0}, + {VC_CONTAINER_CODEC_MP4V, "V_MPEG4/ISO/AP", 0}, + {VC_CONTAINER_CODEC_DIV3, "V_MPEG4/MS/V3", 0}, + {VC_CONTAINER_CODEC_H264, "V_MPEG4/ISO/AVC", VC_CONTAINER_VARIANT_H264_AVC1}, + {VC_CONTAINER_CODEC_MJPEG, "V_MJPEG", 0}, + {VC_CONTAINER_CODEC_RV10, "V_REAL/RV10", 0}, + {VC_CONTAINER_CODEC_RV20, "V_REAL/RV20", 0}, + {VC_CONTAINER_CODEC_RV30, "V_REAL/RV30", 0}, + {VC_CONTAINER_CODEC_RV40, "V_REAL/RV40", 0}, + {VC_CONTAINER_CODEC_THEORA, "V_THEORA", 0}, + {VC_CONTAINER_CODEC_DIRAC, "V_DIRAC", 0}, + {VC_CONTAINER_CODEC_VP8, "V_VP8", 0}, + + /* Audio */ + {VC_CONTAINER_CODEC_MPGA, "A_MPEG/L3", VC_CONTAINER_VARIANT_MPGA_L3}, + {VC_CONTAINER_CODEC_MPGA, "A_MPEG/L2", VC_CONTAINER_VARIANT_MPGA_L2}, + {VC_CONTAINER_CODEC_MPGA, "A_MPEG/L1", VC_CONTAINER_VARIANT_MPGA_L1}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG2/MAIN", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG2/LC", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG2/SSR", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG2/LC/SBR", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG4/MAIN", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG4/LC", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG4/SSR", 0}, + {VC_CONTAINER_CODEC_MP4A, "A_AAC/MPEG4/LC/SBR", 0}, + {VC_CONTAINER_CODEC_AC3, "A_AC3", 0}, + {VC_CONTAINER_CODEC_EAC3, "A_EAC3", 0}, + {VC_CONTAINER_CODEC_DTS, "A_DTS", 0}, + {VC_CONTAINER_CODEC_MLP, "A_MLP", 0}, + {0, "A_TRUEHD", 0}, + {VC_CONTAINER_CODEC_VORBIS, "A_VORBIS", 0}, + {VC_CONTAINER_CODEC_FLAC, "A_FLAC", 0}, + {VC_CONTAINER_CODEC_PCM_SIGNED_LE, "A_PCM/INT/LIT", 0}, + {VC_CONTAINER_CODEC_PCM_SIGNED_BE, "A_PCM/INT/BIG", 0}, + {VC_CONTAINER_CODEC_PCM_FLOAT_LE, "A_PCM/FLOAT/IEEE", 0}, + {0, "A_REAL/xyzt", 0}, + {0, "A_REAL/14_4", 0}, + + /* Text */ + {VC_CONTAINER_CODEC_TEXT, "S_TEXT/ASCII", 0}, + {VC_CONTAINER_CODEC_TEXT, "S_TEXT/UTF8", 0}, + {VC_CONTAINER_CODEC_SSA, "S_TEXT/ASS", 0}, + {VC_CONTAINER_CODEC_SSA, "S_TEXT/SSA", 0}, + {VC_CONTAINER_CODEC_SSA, "S_ASS", 0}, + {VC_CONTAINER_CODEC_SSA, "S_SSA", 0}, + {VC_CONTAINER_CODEC_USF, "S_TEXT/USF", 0}, + {VC_CONTAINER_CODEC_VOBSUB, "S_VOBSUB", 0}, + + {0, 0} +}; + +/****************************************************************************** +Local Functions +******************************************************************************/ + +static VC_CONTAINER_FOURCC_T mkv_codecid_to_fourcc(const char *codecid, + VC_CONTAINER_FOURCC_T *variant) +{ + unsigned int i; + for(i = 0; codecid_to_fourcc_table[i].codecid; i++) + if(!strcmp(codecid_to_fourcc_table[i].codecid, codecid)) break; + if (variant) *variant = codecid_to_fourcc_table[i].variant; + return codecid_to_fourcc_table[i].fourcc; +} + +#if 0 +/** Find the track associated with an MKV track number */ +static VC_CONTAINER_TRACK_T *mkv_reader_find_track( VC_CONTAINER_T *p_ctx, unsigned int mkv_track_num) +{ + VC_CONTAINER_TRACK_T *p_track = 0; + unsigned int i; + + for(i = 0; i < p_ctx->tracks_num; i++) + if(p_ctx->tracks[i]->priv->module->number == mkv_track_num) break; + + if(i < p_ctx->tracks_num) /* We found it */ + p_track = p_ctx->tracks[i]; + + return p_track; +} +#endif + +/** Base function used to read an MKV/EBML element header. + * This will read the element header do lots of sanity checking and return the element id + * and the size of the data contained in the element */ +static VC_CONTAINER_STATUS_T mkv_read_element_header(VC_CONTAINER_T *p_ctx, int64_t size, + MKV_ELEMENT_ID_T *id, int64_t *element_size, MKV_ELEMENT_ID_T parent_id, + MKV_ELEMENT_T **elem) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + MKV_ELEMENT_T *element; + + module->element_offset = STREAM_POSITION(p_ctx); + + *id = MKV_READ_ID(p_ctx, "Element ID"); + CHECK_POINT(p_ctx); + if(!*id) + { + LOG_DEBUG(p_ctx, "invalid element id %i", *id); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + if(elem) element = *elem; + else element = mkv_elements_list; + + /* Find out which Element we are dealing with */ + while(element->id && *id != element->id) element++; + + *element_size = MKV_READ_UINT(p_ctx, "Element Size"); + CHECK_POINT(p_ctx); + LOG_FORMAT(p_ctx, "- Element %s (ID 0x%x), Size: %"PRIi64", Offset: %"PRIi64, + element->psz_name, *id, *element_size, module->element_offset); + + /* Sanity check the element size */ + if(*element_size + 1 < 0 /* Shouldn't ever get that big */ || + /* Only the segment / cluster elements can really be massive */ + (*id != MKV_ELEMENT_ID_SEGMENT && *id != MKV_ELEMENT_ID_CLUSTER && + *element_size > MKV_MAX_ELEMENT_SIZE)) + { + LOG_DEBUG(p_ctx, "element %s has an invalid size (%"PRIi64")", + element->psz_name, *element_size); + return VC_CONTAINER_ERROR_CORRUPTED; + } + if(size >= 0 && *element_size > size) + { + LOG_DEBUG(p_ctx, "element %s is bigger than it should (%"PRIi64" > %"PRIi64")", + element->psz_name, *element_size, size); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + /* Sanity check that the element has the right parent */ + if(element->id && element->parent_id != MKV_ELEMENT_ID_INVALID && + parent_id != MKV_ELEMENT_ID_INVALID && parent_id != element->parent_id) + { + LOG_FORMAT(p_ctx, "Ignoring mis-placed element %s (ID 0x%x)", element->psz_name, *id); + while(element->id != MKV_ELEMENT_ID_UNKNOWN) element++; + } + + /* Sanity check that the element isn't too deeply nested */ + if(module->element_level >= MKV_MAX_ELEMENT_LEVEL) + { + LOG_DEBUG(p_ctx, "element %s is too deep. skipping", element->psz_name); + while(element->id != MKV_ELEMENT_ID_UNKNOWN) element++; + } + + if(elem) *elem = element; + return STREAM_STATUS(p_ctx); +} + +static VC_CONTAINER_STATUS_T mkv_read_element_data(VC_CONTAINER_T *p_ctx, + MKV_ELEMENT_T *element, int64_t element_size, int64_t size) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t offset; + + offset = STREAM_POSITION(p_ctx); + if (size < 0) size = element_size; + if (size < 0) size = INT64_C(1) << 62; + + /* Call the element specific parsing function */ + if(element->pf_func) + status = element->pf_func(p_ctx, element->id, element_size < 0 ? size : element_size); + + if(status != VC_CONTAINER_SUCCESS) + LOG_DEBUG(p_ctx, "element %s appears to be corrupted (%i)", element->psz_name, status); + + if(element_size < 0) return STREAM_STATUS(p_ctx); /* Unknown size */ + + /* Skip the rest of the element */ + element_size -= (STREAM_POSITION(p_ctx) - offset); + if(element_size < 0) /* Check for overruns */ + { + /* Things have gone really bad here and we ended up reading past the end of the + * element. We could maybe try to be clever and recover by seeking back to the end + * of the element. However if we get there, the file is clearly corrupted so there's + * no guarantee it would work anyway. */ + LOG_DEBUG(p_ctx, "%"PRIi64" bytes overrun past the end of element %s", + -element_size, element->psz_name); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + if(element_size) + LOG_FORMAT(p_ctx, "%"PRIi64" bytes left unread in element %s", element_size, element->psz_name); + + if(element_size < MKV_MAX_ELEMENT_SIZE) SKIP_BYTES(p_ctx, element_size); + else SEEK(p_ctx, STREAM_POSITION(p_ctx) + element_size); + + return STREAM_STATUS(p_ctx); +} + +/** Base function used to read an MKV/EBML element. + * This will read the element header do lots of sanity checking and pass on the rest + * of the reading to the element specific reading function */ +static VC_CONTAINER_STATUS_T mkv_read_element(VC_CONTAINER_T *p_ctx, + int64_t size, MKV_ELEMENT_ID_T parent_id) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + MKV_ELEMENT_T *element = p_ctx->priv->module->elements_list; + int64_t element_size; + MKV_ELEMENT_ID_T id; + + status = mkv_read_element_header(p_ctx, size, &id, &element_size, parent_id, &element); + if(status != VC_CONTAINER_SUCCESS) return status; + return mkv_read_element_data(p_ctx, element, element_size, size); +} + +/** Reads an unsigned integer element */ +static VC_CONTAINER_STATUS_T mkv_read_element_data_uint(VC_CONTAINER_T *p_ctx, + int64_t size, uint64_t *value) +{ + switch(size) + { + case 1: *value = READ_U8(p_ctx, "u8-integer"); break; + case 2: *value = READ_U16(p_ctx, "u16-integer"); break; + case 3: *value = READ_U24(p_ctx, "u24-integer"); break; + case 4: *value = READ_U32(p_ctx, "u32-integer"); break; + case 5: *value = READ_U40(p_ctx, "u40-integer"); break; + case 6: *value = READ_U48(p_ctx, "u48-integer"); break; + case 7: *value = READ_U56(p_ctx, "u56-integer"); break; + case 8: *value = READ_U64(p_ctx, "u64-integer"); break; + default: return VC_CONTAINER_ERROR_CORRUPTED; + } + return STREAM_STATUS(p_ctx); +} + +/** Reads a float element */ +static VC_CONTAINER_STATUS_T mkv_read_element_data_float(VC_CONTAINER_T *p_ctx, + int64_t size, double *value) +{ + union { + uint32_t u32; + uint64_t u64; + float f; + double d; + } u; + + switch(size) + { + case 4: u.u32 = READ_U32(p_ctx, "f32-float"); *value = u.f; break; + case 8: u.u64 = READ_U64(p_ctx, "f64-float"); *value = u.d; break; + default: return VC_CONTAINER_ERROR_CORRUPTED; + } + LOG_FORMAT(p_ctx, "float: %f", *value); + return STREAM_STATUS(p_ctx); +} + +/** Reads an MKV EBML element */ +static VC_CONTAINER_STATUS_T mkv_read_element_ebml(VC_CONTAINER_T *p_ctx, + MKV_ELEMENT_ID_T id, int64_t size) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t offset = STREAM_POSITION(p_ctx); + + /* Read contained elements */ + module->element_level++; + while(status == VC_CONTAINER_SUCCESS && size >= MKV_ELEMENT_MIN_HEADER_SIZE) + { + offset = STREAM_POSITION(p_ctx); + status = mkv_read_element(p_ctx, size, id); + size -= (STREAM_POSITION(p_ctx) - offset); + } + module->element_level--; + return status; +} + +/** Reads the MKV EBML sub-element */ +static VC_CONTAINER_STATUS_T mkv_read_subelements_ebml( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint64_t value; + + /* Deal with DocType first since it's a special case */ + if(id == MKV_ELEMENT_ID_DOCTYPE) + { + char doctype[] = "matroska doctype"; + + /* Check we've got the right doctype string for matroska */ + if(size <= 0) goto unknown_doctype; + if(size > (int)sizeof(doctype)) goto unknown_doctype; + if((int)READ_STRING(p_ctx, doctype, size, "string") != size) return STREAM_STATUS(p_ctx); + if((size != sizeof("matroska")-1 || strncmp(doctype, "matroska", (int)size)) && + (size != sizeof("webm")-1 || strncmp(doctype, "webm", (int)size))) + goto unknown_doctype; + + module->is_doctype_valid = true; + return VC_CONTAINER_SUCCESS; + + unknown_doctype: + LOG_DEBUG(p_ctx, "invalid doctype"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + /* The rest are just unsigned integers */ + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + switch(id) + { + case MKV_ELEMENT_ID_EBML_VERSION: + case MKV_ELEMENT_ID_EBML_READ_VERSION: + if(value != 1) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + break; + case MKV_ELEMENT_ID_EBML_MAX_ID_LENGTH: + if(value > 4) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + break; + case MKV_ELEMENT_ID_EBML_MAX_SIZE_LENGTH: + if(value > 8) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + break; + case MKV_ELEMENT_ID_DOCTYPE_VERSION: + case MKV_ELEMENT_ID_DOCTYPE_READ_VERSION: + default: break; + } + + return STREAM_STATUS(p_ctx); +} + +static VC_CONTAINER_STATUS_T mkv_read_elements( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t offset = STREAM_POSITION(p_ctx); + bool unknown_size = size < 0; + + /* Read contained elements */ + module->element_level++; + while(status == VC_CONTAINER_SUCCESS && + (unknown_size || size >= MKV_ELEMENT_MIN_HEADER_SIZE)) + { + offset = STREAM_POSITION(p_ctx); + status = mkv_read_element(p_ctx, size, id); + if(!unknown_size) size -= (STREAM_POSITION(p_ctx) - offset); + } + module->element_level--; + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_element_segment( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t offset = STREAM_POSITION(p_ctx); + bool unknown_size = size < 0; + + /* Read contained elements */ + /* Initialise state used by reader */ + module->state.level = 0; + module->state.levels[0].offset = STREAM_POSITION(p_ctx); + module->state.levels[0].size = size; + module->state.levels[0].id = MKV_ELEMENT_ID_SEGMENT; + module->state.levels[0].data_start = 0; + module->state.levels[0].data_offset = 0; + module->timecode_scale = 1000000; + module->duration = 0.0; + module->segment_offset = STREAM_POSITION(p_ctx); + module->segment_size = size; + + /* Read contained elements until we have all the information we need to start + * playing the stream */ + module->element_level++; + while(status == VC_CONTAINER_SUCCESS && + (unknown_size || size >= MKV_ELEMENT_MIN_HEADER_SIZE)) + { + MKV_ELEMENT_T *child = mkv_elements_list; + MKV_ELEMENT_ID_T child_id; + int64_t child_size; + + offset = STREAM_POSITION(p_ctx); + + status = mkv_read_element_header(p_ctx, size, &child_id, &child_size, id, &child); + if(status != VC_CONTAINER_SUCCESS) break; + + if(child_id == MKV_ELEMENT_ID_CLUSTER) + { + /* We found the start of the data */ + module->cluster_offset = module->element_offset; + module->state.level = 1; + module->state.levels[1].offset = STREAM_POSITION(p_ctx); + module->state.levels[1].size = child_size; + module->state.levels[1].id = MKV_ELEMENT_ID_CLUSTER; + module->state.levels[1].data_start = 0; + module->state.levels[1].data_offset = 0; + break; + } + + status = mkv_read_element_data(p_ctx, child, child_size, size); + if(!unknown_size) size -= (STREAM_POSITION(p_ctx) - offset); + } + + module->element_level--; + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_element_track_entry( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_ES_TYPE_T es_type = VC_CONTAINER_ES_TYPE_UNKNOWN; + VC_CONTAINER_TRACK_MODULE_T *track_module; + VC_CONTAINER_TRACK_T *track; + VC_CONTAINER_FOURCC_T fourcc = 0, variant = 0; + unsigned int i, extra_size = 0, extra_offset = 0, is_wf = 0, is_bmih = 0; + + /* Allocate and initialise track data */ + if(p_ctx->tracks_num >= MKV_TRACKS_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + p_ctx->tracks[p_ctx->tracks_num] = track = + vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + module->parsing = track; + track_module = track->priv->module; + track->is_enabled = true; + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + track_module->timecode_scale = 1.0; + track_module->es_type.video.frame_rate = 0; + + status = mkv_read_elements( p_ctx, id, size ); + if(status != VC_CONTAINER_SUCCESS) goto error; + + /* Sanity check the data we got from the track entry */ + if(!track_module->number || !track_module->type) + { status = VC_CONTAINER_ERROR_FORMAT_INVALID; goto error; } + + /* Check the encodings for the track are supported */ + if(track_module->encodings_num > MKV_MAX_ENCODINGS) + { status = VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; goto error; } + for(i = 0; i < track_module->encodings_num; i++) + { + if(track_module->encodings[i].type != MKV_CONTENT_ENCODING_COMPRESSION_HEADER || + !track_module->encodings[i].data_size) + { status = VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; goto error; } + } + + /* Find out the track type */ + if(track_module->type == 0x1) + es_type = VC_CONTAINER_ES_TYPE_VIDEO; + else if(track_module->type == 0x2) + es_type = VC_CONTAINER_ES_TYPE_AUDIO; + else if(track_module->type == 0x11) + es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE; + + if(es_type == VC_CONTAINER_ES_TYPE_UNKNOWN) + { status = VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; goto error; } + + if(!strcmp(track_module->codecid, "V_MS/VFW/FOURCC")) + { + if(vc_container_bitmapinfoheader_to_es_format(track->format->extradata, + track->format->extradata_size, &extra_offset, &extra_size, + track->format) == VC_CONTAINER_SUCCESS) + { + fourcc = track->format->codec; + is_bmih = 1; + } + track->format->extradata += extra_offset; + track->format->extradata_size = extra_size; + } + else if(!strcmp(track_module->codecid, "A_MS/ACM")) + { + if(vc_container_waveformatex_to_es_format(track->format->extradata, + track->format->extradata_size, &extra_offset, &extra_size, + track->format) == VC_CONTAINER_SUCCESS) + { + fourcc = track->format->codec; + is_wf = 1; + } + track->format->extradata += extra_offset; + track->format->extradata_size = extra_size; + } + else if((!strncmp(track_module->codecid, "A_AAC/MPEG2/", sizeof("A_AAC/MPEG2/")-1) || + !strncmp(track_module->codecid, "A_AAC/MPEG4/", sizeof("A_AAC/MPEG4/")-1)) && + !track->format->extradata_size) + { + static const unsigned int sample_rates[16] = + {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}; + unsigned int samplerate, samplerate_idx, profile, sbr = 0; + uint8_t *extra; + + fourcc = mkv_codecid_to_fourcc(track_module->codecid, &variant); + + /* Create extra data */ + if( !strcmp( &track_module->codecid[12], "MAIN" ) ) profile = 0; + else if( !strcmp( &track_module->codecid[12], "LC" ) ) profile = 1; + else if( !strcmp( &track_module->codecid[12], "SSR" ) ) profile = 2; + else if( !strcmp( &track_module->codecid[12], "LC/SBR" ) ) { profile = 1; sbr = 1; } + else profile = 3; + + samplerate = track_module->es_type.audio.sampling_frequency; + for( samplerate_idx = 0; samplerate_idx < 13; samplerate_idx++ ) + if( sample_rates[samplerate_idx] == samplerate ) break; + + status = vc_container_track_allocate_extradata(p_ctx, track, sbr ? 5 : 2); + if(status != VC_CONTAINER_SUCCESS) goto error; + track->format->extradata_size = sbr ? 5 : 2; + extra = track->format->extradata; + + extra[0] = ((profile + 1) << 3) | ((samplerate_idx & 0xe) >> 1); + extra[1] = ((samplerate_idx & 0x1) << 7) | (track_module->es_type.audio.channels << 3); + + if(sbr) + { + unsigned int sync_extension_type = 0x2B7; + samplerate = track_module->es_type.audio.output_sampling_frequency; + for(samplerate_idx = 0; samplerate_idx < 13; samplerate_idx++) + if(sample_rates[samplerate_idx] == samplerate) break; + extra[2] = (sync_extension_type >> 3) & 0xFF; + extra[3] = ((sync_extension_type & 0x7) << 5) | 5; + extra[4] = (1 << 7) | (samplerate_idx << 3); + } + } + else fourcc = mkv_codecid_to_fourcc(track_module->codecid, &variant); + + if(!fourcc) + { + LOG_DEBUG(p_ctx, "codec id %s is not supported", track_module->codecid); + } + + LOG_DEBUG(p_ctx, "found track %4.4s", (char *)&fourcc); + track->format->codec = fourcc; + track->format->codec_variant = variant; + track->format->es_type = es_type; + + switch(es_type) + { + case VC_CONTAINER_ES_TYPE_VIDEO: + if(!is_bmih) + { + track->format->type->video.width = track_module->es_type.video.pixel_width; + track->format->type->video.height = track_module->es_type.video.pixel_height; + } + track->format->type->video.visible_width = track->format->type->video.width; + track->format->type->video.visible_height = track->format->type->video.height; + if(track_module->es_type.video.pixel_crop_left < track->format->type->video.visible_width && + track_module->es_type.video.pixel_crop_top < track->format->type->video.visible_height) + { + track->format->type->video.x_offset = track_module->es_type.video.pixel_crop_left; + track->format->type->video.y_offset = track_module->es_type.video.pixel_crop_right; + track->format->type->video.visible_width -= track->format->type->video.x_offset; + track->format->type->video.visible_height -= track->format->type->video.y_offset; + } + if(track_module->es_type.video.pixel_crop_right < track->format->type->video.visible_width && + track_module->es_type.video.pixel_crop_bottom < track->format->type->video.visible_height) + { + track->format->type->video.visible_width -= track_module->es_type.video.pixel_crop_right; + track->format->type->video.visible_height -= track_module->es_type.video.pixel_crop_bottom; + } + if(track_module->es_type.video.frame_rate) + { + track->format->type->video.frame_rate_den = 100; + track->format->type->video.frame_rate_num = 100 * track_module->es_type.video.frame_rate; + } + if(track_module->es_type.video.display_width && track_module->es_type.video.display_height) + { + track->format->type->video.par_num = track_module->es_type.video.display_width * + track->format->type->video.visible_height; + track->format->type->video.par_den = track_module->es_type.video.display_height * + track->format->type->video.visible_width; + vc_container_maths_rational_simplify(&track->format->type->video.par_num, + &track->format->type->video.par_den); + } + break; + case VC_CONTAINER_ES_TYPE_AUDIO: + if(is_wf) break; + track->format->type->audio.sample_rate = track_module->es_type.audio.sampling_frequency; + if(track_module->es_type.audio.output_sampling_frequency) + track->format->type->audio.sample_rate = track_module->es_type.audio.output_sampling_frequency; + track->format->type->audio.channels = track_module->es_type.audio.channels; + track->format->type->audio.bits_per_sample = track_module->es_type.audio.bit_depth; + break; + default: + case VC_CONTAINER_ES_TYPE_SUBPICTURE: + track->format->type->subpicture.encoding = VC_CONTAINER_CHAR_ENCODING_UTF8; + if(!strcmp(track_module->codecid, "S_TEXT/ASCII")) + track->format->type->subpicture.encoding = VC_CONTAINER_CHAR_ENCODING_UNKNOWN; + } + + track->is_enabled = true; + + p_ctx->tracks_num++; + return VC_CONTAINER_SUCCESS; + + error: + for(i = 0; i < MKV_MAX_ENCODINGS; i++) free(track_module->encodings[i].data); + vc_container_free_track(p_ctx, track); + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_track_entry( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = module->parsing; + VC_CONTAINER_TRACK_MODULE_T *track_module = track->priv->module; + uint64_t value; + + /* Deal with string elements */ + if( id == MKV_ELEMENT_ID_NAME || + id == MKV_ELEMENT_ID_LANGUAGE || + id == MKV_ELEMENT_ID_TRACK_CODEC_ID || + id == MKV_ELEMENT_ID_TRACK_CODEC_NAME ) + { + char stringbuf[MKV_MAX_STRING_SIZE+1]; + + if(size > MKV_MAX_STRING_SIZE) + { + LOG_DEBUG(p_ctx, "string truncated (%i>%i)", (int)size, MKV_MAX_STRING_SIZE); + size = MKV_MAX_STRING_SIZE; + } + if(READ_BYTES(p_ctx, stringbuf, size) != (size_t)size) + { + //XXX do sane thing + return STREAM_STATUS(p_ctx); + } + stringbuf[size] = 0; /* null terminate */ + + LOG_FORMAT(p_ctx, "%s", stringbuf); + + if(id == MKV_ELEMENT_ID_TRACK_CODEC_ID) + strncpy(track_module->codecid, stringbuf, MKV_CODECID_MAX-1); + + return VC_CONTAINER_SUCCESS; + } + + /* Deal with codec private data */ + if( id == MKV_ELEMENT_ID_TRACK_CODEC_PRIVATE ) + { + status = vc_container_track_allocate_extradata(p_ctx, track, (unsigned int)size); + if(status != VC_CONTAINER_SUCCESS) return status; + track->format->extradata_size = READ_BYTES(p_ctx, track->format->extradata, size); + return STREAM_STATUS(p_ctx); + } + + /* The rest are just unsigned integers */ + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + switch(id) + { + case MKV_ELEMENT_ID_TRACK_NUMBER: + track_module->number = value; break; + case MKV_ELEMENT_ID_TRACK_TYPE: + track_module->type = value; break; + case MKV_ELEMENT_ID_DEFAULT_DURATION: + track_module->frame_duration = value; break; + default: break; + } + + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_video( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = module->parsing->priv->module; + uint64_t value; + + /* Deal with floating point values first */ + if(id == MKV_ELEMENT_ID_FRAME_RATE) + { + double fvalue; + status = mkv_read_element_data_float(p_ctx, size, &fvalue); + if(status != VC_CONTAINER_SUCCESS) return status; + track_module->es_type.video.frame_rate = fvalue; + return status; + } + + /* The rest are just unsigned integers */ + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + switch(id) + { + case MKV_ELEMENT_ID_PIXEL_WIDTH: track_module->es_type.video.pixel_width = value; break; + case MKV_ELEMENT_ID_PIXEL_HEIGHT: track_module->es_type.video.pixel_height = value; break; + case MKV_ELEMENT_ID_PIXEL_CROP_BOTTOM: track_module->es_type.video.pixel_crop_bottom = value; break; + case MKV_ELEMENT_ID_PIXEL_CROP_TOP: track_module->es_type.video.pixel_crop_top = value; break; + case MKV_ELEMENT_ID_PIXEL_CROP_LEFT: track_module->es_type.video.pixel_crop_left = value; break; + case MKV_ELEMENT_ID_PIXEL_CROP_RIGHT: track_module->es_type.video.pixel_crop_right = value; break; + case MKV_ELEMENT_ID_DISPLAY_WIDTH: track_module->es_type.video.display_width = value; break; + case MKV_ELEMENT_ID_DISPLAY_HEIGHT: track_module->es_type.video.display_height = value; break; + case MKV_ELEMENT_ID_DISPLAY_UNIT: track_module->es_type.video.display_unit = value; break; + case MKV_ELEMENT_ID_ASPECT_RATIO_TYPE: track_module->es_type.video.aspect_ratio_type = value; break; + default: break; + } + + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_audio( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = module->parsing->priv->module; + uint64_t value; + + /* Deal with floating point values first */ + if(id == MKV_ELEMENT_ID_SAMPLING_FREQUENCY || + id == MKV_ELEMENT_ID_OUTPUT_SAMPLING_FREQUENCY) + { + double fvalue; + status = mkv_read_element_data_float(p_ctx, size, &fvalue); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(id == MKV_ELEMENT_ID_SAMPLING_FREQUENCY) + track_module->es_type.audio.sampling_frequency = fvalue; + + if(id == MKV_ELEMENT_ID_OUTPUT_SAMPLING_FREQUENCY) + track_module->es_type.audio.output_sampling_frequency = fvalue; + + return status; + } + + /* The rest are just unsigned integers */ + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + switch(id) + { + case MKV_ELEMENT_ID_CHANNELS: track_module->es_type.audio.channels = value; break; + case MKV_ELEMENT_ID_BIT_DEPTH: track_module->es_type.audio.bit_depth = value; break; + default: break; + } + + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_element_encoding( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = module->parsing->priv->module; + VC_CONTAINER_STATUS_T status; + status = mkv_read_elements(p_ctx, id, size); + track_module->encodings_num++; + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_encoding( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = module->parsing->priv->module; + VC_CONTAINER_STATUS_T status; + uint64_t value; + + /* These are just unsigned integers */ + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(track_module->encodings_num >= MKV_MAX_ENCODINGS) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + switch(id) + { + case MKV_ELEMENT_ID_CONTENT_ENCODING_TYPE: + if(value == 0) track_module->encodings[track_module->encodings_num].type = MKV_CONTENT_ENCODING_COMPRESSION_ZLIB; + if(value == 1) track_module->encodings[track_module->encodings_num].type = MKV_CONTENT_ENCODING_ENCRYPTION; + else track_module->encodings[track_module->encodings_num].type = MKV_CONTENT_ENCODING_UNKNOWN; + break; + default: break; + } + + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_compression( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = module->parsing->priv->module; + VC_CONTAINER_STATUS_T status; + uint64_t value; + uint8_t *data; + + if(id == MKV_ELEMENT_ID_CONTENT_COMPRESSION_ALGO) + { + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(value == 0) track_module->encodings[track_module->encodings_num].type = MKV_CONTENT_ENCODING_COMPRESSION_ZLIB; + if(value == 3) track_module->encodings[track_module->encodings_num].type = MKV_CONTENT_ENCODING_COMPRESSION_HEADER; + else track_module->encodings[track_module->encodings_num].type = MKV_CONTENT_ENCODING_UNKNOWN; + return status; + } + + if(id == MKV_ELEMENT_ID_CONTENT_COMPRESSION_SETTINGS) + { + if(track_module->encodings[track_module->encodings_num].type == MKV_CONTENT_ENCODING_COMPRESSION_HEADER) + { + if(size > MKV_MAX_ENCODING_DATA) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + data = malloc((int)size); + if(!data) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + track_module->encodings[track_module->encodings_num].data = data; + track_module->encodings[track_module->encodings_num].data_size = READ_BYTES(p_ctx, data, size); + if(track_module->encodings[track_module->encodings_num].data_size != size) + track_module->encodings[track_module->encodings_num].data_size = 0; + return STREAM_STATUS(p_ctx); + } + else + { + SKIP_BYTES(p_ctx, size); + } + return STREAM_STATUS(p_ctx); + } + + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_info( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint64_t value; + double fvalue; + + switch(id) + { + case MKV_ELEMENT_ID_TIMECODE_SCALE: + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) break; + module->timecode_scale = value; + break; + case MKV_ELEMENT_ID_DURATION: + status = mkv_read_element_data_float(p_ctx, size, &fvalue); + if(status != VC_CONTAINER_SUCCESS) break; + module->duration = fvalue; + break; + case MKV_ELEMENT_ID_TITLE: + case MKV_ELEMENT_ID_MUXING_APP: + case MKV_ELEMENT_ID_WRITING_APP: + SKIP_STRING(p_ctx, size, "string"); + break; + + default: break; + } + + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_seek_head( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint64_t value; + + if(id == MKV_ELEMENT_ID_SEEK) + { + module->seekhead_elem_id = MKV_ELEMENT_ID_UNKNOWN; + module->seekhead_elem_offset = -1; + status = mkv_read_elements(p_ctx, id, size); + if(status == VC_CONTAINER_SUCCESS && !module->cues_offset && + module->seekhead_elem_id == MKV_ELEMENT_ID_CUES && module->seekhead_elem_offset) + module->cues_offset = module->seekhead_elem_offset; + if(status == VC_CONTAINER_SUCCESS && !module->tags_offset && + module->seekhead_elem_id == MKV_ELEMENT_ID_TAGS && module->seekhead_elem_offset) + module->tags_offset = module->seekhead_elem_offset; + return status; + } + + if(id == MKV_ELEMENT_ID_SEEK_ID) + { + MKV_ELEMENT_T *element = mkv_elements_list; + id = MKV_READ_ID(p_ctx, "Element ID"); + module->seekhead_elem_id = id; + + /* Find out which Element we are dealing with */ + while(element->id && id != element->id) element++; + LOG_FORMAT(p_ctx, "element: %s (ID 0x%x)", element->psz_name, id); + } + else if(id == MKV_ELEMENT_ID_SEEK_POSITION) + { + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + LOG_FORMAT(p_ctx, "offset: %"PRIi64, value + module->segment_offset); + module->seekhead_elem_offset = value + module->segment_offset; + } + + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_element_cues( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_PARAM_UNUSED(id); + VC_CONTAINER_PARAM_UNUSED(size); + module->cues_offset = module->element_offset; + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_cue_point( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint64_t value; + + /* All the values are unsigned integers */ + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + switch(id) + { + case MKV_ELEMENT_ID_CUE_TIME: + module->cue_timecode = value; break; + case MKV_ELEMENT_ID_CUE_TRACK: + module->cue_track = value; break; + case MKV_ELEMENT_ID_CUE_CLUSTER_POSITION: + module->cue_cluster_offset = value; break; + case MKV_ELEMENT_ID_CUE_BLOCK_NUMBER: + module->cue_block = value; break; + default: break; + } + + return status; +} + +static VC_CONTAINER_STATUS_T mkv_read_subelements_cluster( VC_CONTAINER_T *p_ctx, MKV_ELEMENT_ID_T id, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint64_t value; + + /* The rest are just unsigned integers */ + status = mkv_read_element_data_uint(p_ctx, size, &value); + if(status != VC_CONTAINER_SUCCESS) return status; + + switch(id) + { + //XXX + case MKV_ELEMENT_ID_TIMECODE: module->state.cluster_timecode = value; break; + case MKV_ELEMENT_ID_BLOCK_DURATION: module->state.frame_duration = value; break; + default: break; + } + + return status; +} + +/*******************************/ + +static VC_CONTAINER_STATUS_T mkv_skip_element(VC_CONTAINER_T *p_ctx, + MKV_READER_STATE_T *state) +{ + /* Skip any trailing data from the current element */ + int64_t skip = state->levels[state->level].offset + + state->levels[state->level].size - STREAM_POSITION(p_ctx); + + if(skip < 0) /* Check for overruns */ + { + /* Things have gone really bad here and we ended up reading past the end of the + * element. We could maybe try to be clever and recover by seeking back to the end + * of the element. However if we get there, the file is clearly corrupted so there's + * no guarantee it would work anyway. */ + LOG_DEBUG(p_ctx, "%"PRIi64" bytes overrun past the end of the element", -skip); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + if(skip) + LOG_FORMAT(p_ctx, "%"PRIi64" bytes left unread at the end of element", skip); + + state->level--; + + if (skip >= MKV_MAX_ELEMENT_SIZE) + return SEEK(p_ctx, STREAM_POSITION(p_ctx) + skip); + SKIP_BYTES(p_ctx, skip); + return STREAM_STATUS(p_ctx); +} + +static VC_CONTAINER_STATUS_T mkv_find_next_element(VC_CONTAINER_T *p_ctx, + MKV_READER_STATE_T *state, MKV_ELEMENT_ID_T element_id) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t element_size, element_offset; + MKV_ELEMENT_ID_T id; + + /* Skip all elements until we find the next requested element */ + do + { + MKV_ELEMENT_T *element = mkv_cluster_elements_list; + + /* Check whether we've reach the end of the parent element */ + if(STREAM_POSITION(p_ctx) >= state->levels[state->level].offset + + state->levels[state->level].size) + return VC_CONTAINER_ERROR_NOT_FOUND; + + status = mkv_read_element_header(p_ctx, INT64_C(1) << 30, &id, + &element_size, state->levels[state->level].id, &element); + element_offset = STREAM_POSITION(p_ctx); + if(status != VC_CONTAINER_SUCCESS) return status; + if(id == element_id) break; + if(element_id == MKV_ELEMENT_ID_BLOCKGROUP && id == MKV_ELEMENT_ID_SIMPLE_BLOCK) break; + + if(element_id == MKV_ELEMENT_ID_BLOCK && id == MKV_ELEMENT_ID_REFERENCE_BLOCK) + state->seen_ref_block = 1; + + /* Check whether we've reached the end of the parent element */ + if(STREAM_POSITION(p_ctx) + element_size >= state->levels[state->level].offset + + state->levels[state->level].size) + return VC_CONTAINER_ERROR_NOT_FOUND; + + status = mkv_read_element_data(p_ctx, element, element_size, INT64_C(1) << 30); +#if 0 + if(element_size < MKV_MAX_ELEMENT_SIZE) SKIP_BYTES(p_ctx, element_size); + else SEEK(p_ctx, STREAM_POSITION(p_ctx) + element_size); +#endif + } while (status == VC_CONTAINER_SUCCESS && STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS); + + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) + return STREAM_STATUS(p_ctx); + + state->level++; + state->levels[state->level].offset = element_offset; + state->levels[state->level].size = element_size; + state->levels[state->level].id = id; + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T mkv_find_next_segment(VC_CONTAINER_T *p_ctx, + MKV_READER_STATE_T *state) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t element_size, element_offset; + MKV_ELEMENT_ID_T id; + + /* Skip all elements until we find the next segment */ + do + { + MKV_ELEMENT_T *element = mkv_cluster_elements_list; + + status = mkv_read_element_header(p_ctx, INT64_C(-1), &id, + &element_size, MKV_ELEMENT_ID_INVALID, &element); + + element_offset = STREAM_POSITION(p_ctx); + if(status != VC_CONTAINER_SUCCESS) return status; + if(id == MKV_ELEMENT_ID_SEGMENT) break; + + status = mkv_read_element_data(p_ctx, element, element_size, INT64_C(-1)); + } while (status == VC_CONTAINER_SUCCESS && STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS); + + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) + return STREAM_STATUS(p_ctx); + + state->level++; + state->levels[state->level].offset = element_offset; + state->levels[state->level].size = element_size; + state->levels[state->level].id = id; + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T mkv_find_next_block(VC_CONTAINER_T *p_ctx, MKV_READER_STATE_T *state) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND; + + do + { + if(state->level < 0) + { +#ifdef ENABLE_MKV_EXTRA_LOGGING + LOG_DEBUG(p_ctx, "find segment"); +#endif + status = mkv_find_next_segment(p_ctx, state); + if(status == VC_CONTAINER_SUCCESS) + { + LOG_ERROR(p_ctx, "multi-segment files not supported"); + status = VC_CONTAINER_ERROR_EOS; + break; + } + } + if(state->levels[state->level].id == MKV_ELEMENT_ID_BLOCK || + state->levels[state->level].id == MKV_ELEMENT_ID_SIMPLE_BLOCK) + { + status = mkv_skip_element(p_ctx, state); + } + if(state->levels[state->level].id == MKV_ELEMENT_ID_BLOCKGROUP) + { +#ifdef ENABLE_MKV_EXTRA_LOGGING + LOG_DEBUG(p_ctx, "find block"); +#endif + state->seen_ref_block = 0; + state->frame_duration = 0; + status = mkv_find_next_element(p_ctx, state, MKV_ELEMENT_ID_BLOCK); + if(status == VC_CONTAINER_SUCCESS) break; + + if(status == VC_CONTAINER_ERROR_NOT_FOUND) + status = mkv_skip_element(p_ctx, state); + } + if(state->levels[state->level].id == MKV_ELEMENT_ID_CLUSTER) + { +#ifdef ENABLE_MKV_EXTRA_LOGGING + LOG_DEBUG(p_ctx, "find blockgroup or simpleblock"); +#endif + state->frame_duration = 0; + status = mkv_find_next_element(p_ctx, state, MKV_ELEMENT_ID_BLOCKGROUP); + if(status == VC_CONTAINER_SUCCESS && + state->levels[state->level].id == MKV_ELEMENT_ID_SIMPLE_BLOCK) break; + if(status == VC_CONTAINER_ERROR_NOT_FOUND) + status = mkv_skip_element(p_ctx, state); + } + if(state->levels[state->level].id == MKV_ELEMENT_ID_SEGMENT) + { +#ifdef ENABLE_MKV_EXTRA_LOGGING + LOG_DEBUG(p_ctx, "find cluster"); +#endif + status = mkv_find_next_element(p_ctx, state, MKV_ELEMENT_ID_CLUSTER); + if(status == VC_CONTAINER_ERROR_NOT_FOUND) + status = mkv_skip_element(p_ctx, state); + } + + } while(status == VC_CONTAINER_SUCCESS && STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS); + + return status == VC_CONTAINER_SUCCESS ? STREAM_STATUS(p_ctx) : status; +} + +static VC_CONTAINER_STATUS_T mkv_read_next_frame_header(VC_CONTAINER_T *p_ctx, + MKV_READER_STATE_T *state, uint32_t *pi_track, uint32_t *pi_length) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = 0; + unsigned int i, track, flags, is_first_lace = 0; + int64_t size, pts; + + if ((state->levels[state->level].id == MKV_ELEMENT_ID_BLOCK || + state->levels[state->level].id == MKV_ELEMENT_ID_SIMPLE_BLOCK) && + state->levels[state->level].data_start + state->levels[state->level].data_offset < + state->levels[state->level].size) + goto end; + + status = mkv_find_next_block(p_ctx, state); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* We have a block */ + size = state->levels[state->level].size; + track = MKV_READ_UINT(p_ctx, "Track Number"); LOG_FORMAT(p_ctx, "Track Number: %u", track); + pts = (int16_t)MKV_READ_U16(p_ctx, "Timecode"); + flags = MKV_READ_U8(p_ctx, "Flags"); + if(state->levels[state->level].id == MKV_ELEMENT_ID_BLOCK) flags &= 0x0F; + + //TODO improve sanity checking + /* Sanity checking */ + if(size < 0) return VC_CONTAINER_ERROR_CORRUPTED; + if(STREAM_STATUS(p_ctx)) return STREAM_STATUS(p_ctx); + + for (i = 0; i < p_ctx->tracks_num; i++) + if (p_ctx->tracks[i]->priv->module->number == track) + { track_module = p_ctx->tracks[i]->priv->module; break; } + + /* Finding out if we have a keyframe when dealing with an MKV_ELEMENT_ID_BLOCK is tricky */ + if(state->levels[state->level].id == MKV_ELEMENT_ID_BLOCK && + i < p_ctx->tracks_num && p_ctx->tracks[i]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + /* The absence of a ReferenceBlock element tells us if we are a keyframe or not. + * The problem we have is that this element can appear before as well as after the block element. + * To avoid seeking to look for this element, we do some guess work. */ + if(!state->seen_ref_block && state->level && + state->levels[state->level].offset + state->levels[state->level].size >= + state->levels[state->level-1].offset + state->levels[state->level-1].size) + flags |= 0x80; + } + + /* Take care of the lacing */ + state->lacing_num_frames = 0; + if(i < p_ctx->tracks_num && (flags & 0x06)) + { + unsigned int i, value = 0; + int32_t fs = 0; + + state->lacing_num_frames = MKV_READ_U8(p_ctx, "Lacing Head"); + state->lacing_size = 0; + switch((flags & 0x06)>>1) + { + case 1: /* Xiph lacing */ + for(i = 0; i < state->lacing_num_frames; i++, fs = 0) + { + do { + value = vc_container_io_read_uint8(p_ctx->priv->io); + fs += value; size--; + } while(value == 255); + LOG_FORMAT(p_ctx, "Frame Size: %i", (int)fs); + if(state->lacing_num_frames > MKV_MAX_LACING_NUM) continue; + state->lacing_sizes[state->lacing_num_frames-(i+1)] = fs; + } + break; + case 3: /* EBML lacing */ + for(i = 0; i < state->lacing_num_frames; i++) + { + if(!i) fs = MKV_READ_UINT(p_ctx, "Frame Size"); + else fs += MKV_READ_SINT(p_ctx, "Frame Size"); + LOG_FORMAT(p_ctx, "Frame Size: %i", (int)fs); + if(state->lacing_num_frames > MKV_MAX_LACING_NUM) continue; + state->lacing_sizes[state->lacing_num_frames-(i+1)] = fs; + } + break; + default: /* Fixed-size lacing */ + state->lacing_size = size / (state->lacing_num_frames+1); + break; + } + + /* There is a max number of laced frames we can support but after we can still give back + * all the other frames in 1 packet */ + if(state->lacing_num_frames > MKV_MAX_LACING_NUM) + state->lacing_num_frames = MKV_MAX_LACING_NUM; + + /* Sanity check the size of the frames */ + if(size < 0) return VC_CONTAINER_ERROR_CORRUPTED; + if(STREAM_STATUS(p_ctx)) return STREAM_STATUS(p_ctx); + state->lacing_current_size = state->lacing_size; + if(!state->lacing_size) + { + int64_t frames_size = 0; + for(i = state->lacing_num_frames; i > 0; i--) + { + if(frames_size + state->lacing_sizes[i-1] > size) /* return error ? */ + state->lacing_sizes[i-1] = size - frames_size; + frames_size += state->lacing_sizes[i-1]; + } + } + state->lacing_current_size = 0; + state->lacing_num_frames++; /* Will be decremented further down */ + is_first_lace = 1; + } + + state->track = i; + state->pts = (state->cluster_timecode + pts) * module->timecode_scale; + if(track_module) state->pts *= track_module->timecode_scale; + state->pts /= 1000; + state->flags = flags; + + state->frame_duration = state->frame_duration * module->timecode_scale / 1000; + if(state->lacing_num_frames) state->frame_duration /= state->lacing_num_frames; + if(!state->frame_duration && track_module) + state->frame_duration = track_module->frame_duration / 1000; + + state->levels[state->level].data_start = STREAM_POSITION(p_ctx) - + state->levels[state->level].offset; + state->levels[state->level].data_offset = 0; + + /* Deal with header stripping compression */ + state->header_size = 0; + if(track_module && track_module->encodings_num) + { + state->header_size = track_module->encodings[0].data_size; + state->header_data = track_module->encodings[0].data; + } + state->header_size_backup = state->header_size; + + end: + *pi_length = state->levels[state->level].size - state->levels[state->level].data_start - + state->levels[state->level].data_offset + state->header_size; + *pi_track = state->track; + + /* Special case for lacing */ + if(state->lacing_num_frames && + state->levels[state->level].data_offset >= state->lacing_current_size) + { + /* We need to switch to the next lace */ + if(--state->lacing_num_frames) + { + unsigned int lace_size = state->lacing_size; + if(!state->lacing_size) lace_size = state->lacing_sizes[state->lacing_num_frames-1]; + state->lacing_current_size = lace_size; + } + state->levels[state->level].data_start += state->levels[state->level].data_offset; + state->levels[state->level].data_offset = 0; + if(!is_first_lace && state->frame_duration) + state->pts += state->frame_duration; + else if(!is_first_lace) + state->pts = VC_CONTAINER_TIME_UNKNOWN; + + /* Deal with header stripping compression */ + state->header_data -= (state->header_size_backup - state->header_size); + state->header_size = state->header_size_backup; + } + if(state->lacing_num_frames) + *pi_length = state->lacing_current_size - state->levels[state->level].data_offset + state->header_size; + + return status == VC_CONTAINER_SUCCESS ? STREAM_STATUS(p_ctx) : status; +} + +static VC_CONTAINER_STATUS_T mkv_read_frame_data(VC_CONTAINER_T *p_ctx, + MKV_READER_STATE_T *state, uint8_t *p_data, uint32_t *pi_length) +{ + uint64_t size; + uint32_t header_size; + + size = state->levels[state->level].size - state->levels[state->level].data_start - + state->levels[state->level].data_offset; + + /* Special case for lacing */ + if(state->lacing_num_frames) + { + size = state->lacing_current_size - state->levels[state->level].data_offset; + + if(!p_data) + { + size = SKIP_BYTES(p_ctx, size); + state->levels[state->level].data_offset += size; + return STREAM_STATUS(p_ctx); + } + } + + size += state->header_size; + + if(!p_data) return mkv_skip_element(p_ctx, state); + if(size > *pi_length) size = *pi_length; + + header_size = state->header_size; + if(header_size) + { + if(header_size > size) header_size = size; + memcpy(p_data, state->header_data, header_size); + state->header_size -= header_size; + state->header_data += header_size; + size -= header_size; + } + + size = READ_BYTES(p_ctx, p_data + header_size, size); + state->levels[state->level].data_offset += size; + *pi_length = size + header_size; + + return STREAM_STATUS(p_ctx); +} + +/***************************************************************************** + Functions exported as part of the Container Module API + *****************************************************************************/ + +static VC_CONTAINER_STATUS_T mkv_reader_read(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *p_track = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t buffer_size = 0, track = 0, data_size; + MKV_READER_STATE_T *state = &module->state; + + /* If a specific track has been selected, we need to use the track packet state */ + if(flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) + { + p_track = p_ctx->tracks[p_packet->track]; + state = p_track->priv->module->state; + } + + /**/ + if(state->eos) return VC_CONTAINER_ERROR_EOS; + if(state->corrupted) return VC_CONTAINER_ERROR_CORRUPTED; + + /* Look at the next frame header */ + status = mkv_read_next_frame_header(p_ctx, state, &track, &data_size); + if(status == VC_CONTAINER_ERROR_EOS) state->eos = true; + if(status == VC_CONTAINER_ERROR_CORRUPTED) state->corrupted = true; + if(status != VC_CONTAINER_SUCCESS) return status; + + if(track >= p_ctx->tracks_num || !p_ctx->tracks[track]->is_enabled) + { + /* Skip frame */ + status = mkv_read_frame_data(p_ctx, state, 0, &data_size); + if (status != VC_CONTAINER_SUCCESS) return status; + return VC_CONTAINER_ERROR_CONTINUE; + } + + if((flags & VC_CONTAINER_READ_FLAG_SKIP) && !(flags & VC_CONTAINER_READ_FLAG_INFO)) /* Skip packet */ + return mkv_read_frame_data(p_ctx, state, 0, &data_size); + + p_packet->dts = p_packet->pts = state->pts; + p_packet->flags = 0; + if(state->flags & 0x80) p_packet->flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME; + if(!state->levels[state->level].data_offset) p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + p_packet->size = data_size; + p_packet->track = track; + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + return mkv_read_frame_data(p_ctx, state, 0, &data_size ); + else if(flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + /* Read the frame data */ + buffer_size = p_packet->buffer_size; + status = mkv_read_frame_data(p_ctx, state, p_packet->data, &buffer_size); + if(status != VC_CONTAINER_SUCCESS) + { + /* FIXME */ + return status; + } + + p_packet->size = buffer_size; + if(buffer_size != data_size) + p_packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mkv_reader_seek(VC_CONTAINER_T *p_ctx, + int64_t *p_offset, VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + MKV_READER_STATE_T *state = &module->state; + uint64_t offset = 0, prev_offset = 0, position = STREAM_POSITION(p_ctx); + int64_t time_offset = 0, prev_time_offset = 0; + unsigned int i, video_track; + MKV_ELEMENT_T *element = mkv_cue_elements_list; + int64_t size, element_size; + MKV_ELEMENT_ID_T id; + VC_CONTAINER_PARAM_UNUSED(mode); + VC_CONTAINER_PARAM_UNUSED(flags); + + /* Find out if we have a video track */ + for(video_track = 0; video_track < p_ctx->tracks_num; video_track++) + if(p_ctx->tracks[video_track]->is_enabled && + p_ctx->tracks[video_track]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) break; + + if(!*p_offset) goto end; /* Nothing much to do */ + if(!module->cues_offset) {status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; goto error;} + + /* We need to do a search in the cue list */ + status = SEEK(p_ctx, module->cues_offset); + if(status != VC_CONTAINER_SUCCESS) goto error; + + /* First read the header of the cues element */ + status = mkv_read_element_header(p_ctx, INT64_C(-1) /* TODO */, &id, &element_size, + MKV_ELEMENT_ID_SEGMENT, &element); + if(status != VC_CONTAINER_SUCCESS || id != MKV_ELEMENT_ID_CUES) goto error; + size = element_size; + + module->elements_list = mkv_cue_elements_list; + do + { + MKV_ELEMENT_T *element = mkv_cue_elements_list; + int64_t element_offset = STREAM_POSITION(p_ctx); + + /* Exit condition for when we've scanned the whole cues list */ + if(size <= 0) + { + if(!(flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + break; /* Just use the last valid entry in that case */ + status = VC_CONTAINER_ERROR_EOS; + goto error; + } + + status = mkv_read_element_header(p_ctx, size, &id, &element_size, + MKV_ELEMENT_ID_CUES, &element); + size -= STREAM_POSITION(p_ctx) - element_offset; + if(status == VC_CONTAINER_SUCCESS && element->id != MKV_ELEMENT_ID_UNKNOWN) + status = mkv_read_element_data(p_ctx, element, element_size, size); + + if(status != VC_CONTAINER_SUCCESS || element->id == MKV_ELEMENT_ID_UNKNOWN) + { + if(!(flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + break; /* Just use the last valid entry in that case */ + goto error; + } + + size -= element_size; + if(id != MKV_ELEMENT_ID_CUE_POINT) continue; + + /* Ignore cue points which don't belong to the track we want */ + if(video_track != p_ctx->tracks_num && + p_ctx->tracks[video_track]->priv->module->number != module->cue_track) continue; + + time_offset = module->cue_timecode * module->timecode_scale / 1000; + offset = module->cue_cluster_offset; + LOG_DEBUG(p_ctx, "INDEX: %"PRIi64, time_offset); + if( time_offset > *p_offset ) + { + if(!(flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) + { + time_offset = prev_time_offset; + offset = prev_offset; + } + break; + } + prev_time_offset = time_offset; + prev_offset = offset; + } while( 1 ); + module->elements_list = mkv_elements_list; + *p_offset = time_offset; + + end: + /* Try seeking to the requested position */ + status = SEEK(p_ctx, module->segment_offset + offset); + if(status != VC_CONTAINER_SUCCESS && status != VC_CONTAINER_ERROR_EOS) goto error; + + /* Reinitialise the state */ + memset(state, 0, sizeof(*state)); + state->levels[0].offset = module->segment_offset; + state->levels[0].size = module->segment_size; + state->levels[0].id = MKV_ELEMENT_ID_SEGMENT; + if(status == VC_CONTAINER_ERROR_EOS) state->eos = true; + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *p_track = p_ctx->tracks[i]; + p_track->priv->module->state = state; + } + + /* If we have a video track, we skip frames until the next keyframe */ + for(i = 0; video_track != p_ctx->tracks_num && i < 200 /* limit search */; ) + { + uint32_t track, data_size; + status = mkv_read_next_frame_header(p_ctx, state, &track, &data_size); + if(status != VC_CONTAINER_SUCCESS) break; //FIXME + if(track == video_track) i++; + if(track == video_track && (state->flags & 0x80) && + state->pts >= time_offset) break; + + /* Skip frame */ + status = mkv_read_frame_data(p_ctx, state, 0, &data_size); + } + + return VC_CONTAINER_SUCCESS; + + error: + /* Reset everything as it was before the seek */ + SEEK(p_ctx, position); + if(status == VC_CONTAINER_SUCCESS) status = VC_CONTAINER_ERROR_FAILED; + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mkv_reader_close(VC_CONTAINER_T *p_ctx) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i, j; + + for(i = 0; i < p_ctx->tracks_num; i++) + { + for(j = 0; j < MKV_MAX_ENCODINGS; j++) + free(p_ctx->tracks[i]->priv->module->encodings[j].data); + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + } + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T mkv_reader_open(VC_CONTAINER_T *p_ctx) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + uint8_t buffer[4]; + + // Can start with ASCII strings ???? + + /* Check for an EBML element */ + if(PEEK_BYTES(p_ctx, buffer, 4) < 4 || + buffer[0] != 0x1A || buffer[1] != 0x45 || buffer[2] != 0xDF || buffer[3] != 0xA3) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* + * We are dealing with an MKV file + */ + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) {status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error;} + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = module->tracks; + module->elements_list = mkv_elements_list; + + /* Read and sanity check the EBML header */ + status = mkv_read_element(p_ctx, INT64_C(-1), MKV_ELEMENT_ID_UNKNOWN); + if(status != VC_CONTAINER_SUCCESS) goto error; + if(!module->is_doctype_valid) {status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; goto error;} + + /* Read the other elements until we find the start of the data */ + do + { + status = mkv_read_element(p_ctx, INT64_C(-1), MKV_ELEMENT_ID_UNKNOWN); + if(status != VC_CONTAINER_SUCCESS) break; + + if(module->cluster_offset) break; + } while(1); + + /* Bail out if we didn't find a track */ + if(!p_ctx->tracks_num) + { + status = VC_CONTAINER_ERROR_NO_TRACK_AVAILABLE; + goto error; + } + + /* + * We now have all the information we really need to start playing the stream + */ + + p_ctx->priv->pf_close = mkv_reader_close; + p_ctx->priv->pf_read = mkv_reader_read; + p_ctx->priv->pf_seek = mkv_reader_seek; + p_ctx->duration = module->duration / 1000 * module->timecode_scale; + + /* Check if we're done */ + if(!STREAM_SEEKABLE(p_ctx)) + return VC_CONTAINER_SUCCESS; + + if(module->cues_offset && (int64_t)module->cues_offset < p_ctx->size) + p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + + if(module->tags_offset) + { + status = SEEK(p_ctx, module->tags_offset); + if(status == VC_CONTAINER_SUCCESS) + status = mkv_read_element(p_ctx, INT64_C(-1) /*FIXME*/, MKV_ELEMENT_ID_SEGMENT); + } + + /* Seek back to the start of the data */ + return SEEK(p_ctx, module->state.levels[1].offset); + + error: + LOG_DEBUG(p_ctx, "mkv: error opening stream (%i)", status); + if(module) mkv_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open mkv_reader_open +#endif diff --git a/containers/mp4/CMakeLists.txt b/containers/mp4/CMakeLists.txt new file mode 100644 index 0000000..87892c2 --- /dev/null +++ b/containers/mp4/CMakeLists.txt @@ -0,0 +1,19 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_mp4 ${LIBRARY_TYPE} mp4_reader.c) + +target_link_libraries(reader_mp4 containers) + +install(TARGETS reader_mp4 DESTINATION ${VMCS_PLUGIN_DIR}) + +add_library(writer_mp4 ${LIBRARY_TYPE} mp4_writer.c) + +target_link_libraries(writer_mp4 containers) + +install(TARGETS writer_mp4 DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/mp4/mp4_common.h b/containers/mp4/mp4_common.h new file mode 100644 index 0000000..8718535 --- /dev/null +++ b/containers/mp4/mp4_common.h @@ -0,0 +1,128 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef MP4_COMMON_H +#define MP4_COMMON_H + +/****************************************************************************** +Type definitions. +******************************************************************************/ +typedef enum { + MP4_BOX_TYPE_UNKNOWN = 0, + MP4_BOX_TYPE_ROOT = VC_FOURCC('r','o','o','t'), + MP4_BOX_TYPE_FTYP = VC_FOURCC('f','t','y','p'), + MP4_BOX_TYPE_MDAT = VC_FOURCC('m','d','a','t'), + MP4_BOX_TYPE_MOOV = VC_FOURCC('m','o','o','v'), + MP4_BOX_TYPE_MVHD = VC_FOURCC('m','v','h','d'), + MP4_BOX_TYPE_TRAK = VC_FOURCC('t','r','a','k'), + MP4_BOX_TYPE_TKHD = VC_FOURCC('t','k','h','d'), + MP4_BOX_TYPE_MDIA = VC_FOURCC('m','d','i','a'), + MP4_BOX_TYPE_MDHD = VC_FOURCC('m','d','h','d'), + MP4_BOX_TYPE_HDLR = VC_FOURCC('h','d','l','r'), + MP4_BOX_TYPE_MINF = VC_FOURCC('m','i','n','f'), + MP4_BOX_TYPE_VMHD = VC_FOURCC('v','m','h','d'), + MP4_BOX_TYPE_SMHD = VC_FOURCC('s','m','h','d'), + MP4_BOX_TYPE_DINF = VC_FOURCC('d','i','n','f'), + MP4_BOX_TYPE_DREF = VC_FOURCC('d','r','e','f'), + MP4_BOX_TYPE_STBL = VC_FOURCC('s','t','b','l'), + MP4_BOX_TYPE_STSD = VC_FOURCC('s','t','s','d'), + MP4_BOX_TYPE_STTS = VC_FOURCC('s','t','t','s'), + MP4_BOX_TYPE_CTTS = VC_FOURCC('c','t','t','s'), + MP4_BOX_TYPE_STSC = VC_FOURCC('s','t','s','c'), + MP4_BOX_TYPE_STSZ = VC_FOURCC('s','t','s','z'), + MP4_BOX_TYPE_STCO = VC_FOURCC('s','t','c','o'), + MP4_BOX_TYPE_CO64 = VC_FOURCC('c','o','6','4'), + MP4_BOX_TYPE_STSS = VC_FOURCC('s','t','s','s'), + MP4_BOX_TYPE_VIDE = VC_FOURCC('v','i','d','e'), + MP4_BOX_TYPE_SOUN = VC_FOURCC('s','o','u','n'), + MP4_BOX_TYPE_TEXT = VC_FOURCC('t','e','x','t'), + MP4_BOX_TYPE_FREE = VC_FOURCC('f','r','e','e'), + MP4_BOX_TYPE_SKIP = VC_FOURCC('s','k','i','p'), + MP4_BOX_TYPE_WIDE = VC_FOURCC('w','i','d','e'), + MP4_BOX_TYPE_PNOT = VC_FOURCC('p','m','o','t'), + MP4_BOX_TYPE_PICT = VC_FOURCC('P','I','C','T'), + MP4_BOX_TYPE_UDTA = VC_FOURCC('u','d','t','a'), + MP4_BOX_TYPE_UUID = VC_FOURCC('u','u','i','d'), + MP4_BOX_TYPE_ESDS = VC_FOURCC('e','s','d','s'), + MP4_BOX_TYPE_AVCC = VC_FOURCC('a','v','c','C'), + MP4_BOX_TYPE_D263 = VC_FOURCC('d','2','6','3'), + MP4_BOX_TYPE_DAMR = VC_FOURCC('d','a','m','r'), + MP4_BOX_TYPE_DAWP = VC_FOURCC('d','a','w','p'), + MP4_BOX_TYPE_DEVC = VC_FOURCC('d','e','v','c'), + MP4_BOX_TYPE_WAVE = VC_FOURCC('w','a','v','e'), + MP4_BOX_TYPE_ZERO = 0 +} MP4_BOX_TYPE_T; + +typedef enum { + MP4_BRAND_ISOM = VC_FOURCC('i','s','o','m'), + MP4_BRAND_MP42 = VC_FOURCC('m','p','4','2'), + MP4_BRAND_3GP4 = VC_FOURCC('3','g','p','4'), + MP4_BRAND_3GP5 = VC_FOURCC('3','g','p','5'), + MP4_BRAND_3GP6 = VC_FOURCC('3','g','p','6'), + MP4_BRAND_SKM2 = VC_FOURCC('s','k','m','2'), + MP4_BRAND_SKM3 = VC_FOURCC('s','k','m','3'), + MP4_BRAND_QT = VC_FOURCC('q','t',' ',' '), + MP4_BRAND_NUM +} MP4_BRAND_T; + +typedef enum +{ + MP4_SAMPLE_TABLE_STTS = 0, /* decoding time to sample */ + MP4_SAMPLE_TABLE_STSZ = 1, /* sample size */ + MP4_SAMPLE_TABLE_STSC = 2, /* sample to chunk */ + MP4_SAMPLE_TABLE_STCO = 3, /* sample to chunk-offset */ + MP4_SAMPLE_TABLE_STSS = 4, /* sync sample */ + MP4_SAMPLE_TABLE_CO64 = 5, /* sample to chunk-offset */ + MP4_SAMPLE_TABLE_CTTS = 6, /* composite time to sample */ + MP4_SAMPLE_TABLE_NUM +} MP4_SAMPLE_TABLE_T; + +/* Values for object_type_indication (mp4_decoder_config_descriptor) + * see ISO/IEC 14496-1:2001(E) section 8.6.6.2 table 8 p. 30 + * see ISO/IEC 14496-15:2003 (draft) section 4.2.2 table 3 p. 11 + * see SKT Spec 8.2.3 p. 107 + * see 3GPP2 Spec v1.0 p. 22 */ +#define MP4_MPEG4_VISUAL_OBJECT_TYPE 0x20 /* visual ISO/IEC 14496-2 */ +#define MP4_MPEG4_H264_OBJECT_TYPE 0x21 /* visual ISO/IEC 14496-10 */ +#define MP4_MPEG4_H264_PS_OBJECT_TYPE 0x22 /* visual ISO/IEC 14496-10 (used for parameter ES) */ +#define MP4_MPEG4_AAC_LC_OBJECT_TYPE 0x40 /* audio ISO/IEC 14496-3 */ +#define MP4_MPEG2_SP_OBJECT_TYPE 0x60 /* visual ISO/IEC 13818-2 Simple Profile */ +#define MP4_MPEG2_MP_OBJECT_TYPE 0x61 /* visual ISO/IEC 13818-2 Main Profile */ +#define MP4_MPEG2_SNR_OBJECT_TYPE 0x62 /* visual ISO/IEC 13818-2 SNR Profile */ +#define MP4_MPEG2_AAC_LC_OBJECT_TYPE 0x67 /* audio ISO/IEC 13818-7 LowComplexity Profile */ +#define MP4_MP3_OBJECT_TYPE 0x69 /* audio ISO/IEC 13818-3 */ +#define MP4_MPEG1_VISUAL_OBJECT_TYPE 0x6A /* visual ISO/IEC 11172-2 */ +#define MP4_MPEG1_AUDIO_OBJECT_TYPE 0x6B /* audio ISO/IEC 11172-3 */ +#define MP4_JPEG_OBJECT_TYPE 0x6C /* visual ISO/IEC 10918-1 */ +#define MP4_SKT_EVRC_2V1_OBJECT_TYPE 0x82 /* SKT spec V2.1 for EVRC */ +#define MP4_KTF_EVRC_OBJECT_TYPE 0xC2 /* KTF spec V1.2 for EVRC */ +#define MP4_KTF_AMR_OBJECT_TYPE 0xC4 /* KTF spec V1.2 for AMR */ +#define MP4_KTF_MP3_OBJECT_TYPE 0xC5 /* KTF spec V1.2 for MP3 */ +#define MP4_SKT_TEXT_OBJECT_TYPE 0xD0 /* SKT spec V2.2 for Text */ +#define MP4_SKT_EVRC_OBJECT_TYPE 0xD1 /* SKT spec V2.2 for EVRC */ +#define MP4_3GPP2_QCELP_OBJECT_TYPE 0xE1 /* 3GPP2 spec V1.0 for QCELP13K */ + +#endif /* MP4_COMMON_H */ diff --git a/containers/mp4/mp4_reader.c b/containers/mp4/mp4_reader.c new file mode 100644 index 0000000..08341f5 --- /dev/null +++ b/containers/mp4/mp4_reader.c @@ -0,0 +1,1879 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +/* Work-around for MSVC debugger issue */ +#define VC_CONTAINER_MODULE_T VC_CONTAINER_MODULE_MP4_READER_T +#define VC_CONTAINER_TRACK_MODULE_T VC_CONTAINER_TRACK_MODULE_MP4_READER_T + +#define CONTAINER_IS_BIG_ENDIAN +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" +#include "containers/mp4/mp4_common.h" +#undef CONTAINER_HELPER_LOG_INDENT +#define CONTAINER_HELPER_LOG_INDENT(a) (a)->priv->module->box_level + +VC_CONTAINER_STATUS_T mp4_reader_open( VC_CONTAINER_T *p_ctx ); + +/****************************************************************************** +TODO: +- aspect ratio +- itunes gapless +- edit list +- subpicture track +******************************************************************************/ + +/****************************************************************************** +Defines. +******************************************************************************/ +#define MP4_TRACKS_MAX 16 + +#define MP4_BOX_MIN_HEADER_SIZE 8 +#define MP4_MAX_BOX_SIZE (1<<29) /* Does not apply to the mdat box */ +#define MP4_MAX_BOX_LEVEL 10 + +#define MP4_MAX_SAMPLES_BATCH_SIZE (16*1024) + +#define MP4_SKIP_U8(ctx,n) (size -= 1, SKIP_U8(ctx,n)) +#define MP4_SKIP_U16(ctx,n) (size -= 2, SKIP_U16(ctx,n)) +#define MP4_SKIP_U24(ctx,n) (size -= 3, SKIP_U24(ctx,n)) +#define MP4_SKIP_U32(ctx,n) (size -= 4, SKIP_U32(ctx,n)) +#define MP4_SKIP_U64(ctx,n) (size -= 8, SKIP_U64(ctx,n)) +#define MP4_READ_U8(ctx,n) (size -= 1, READ_U8(ctx,n)) +#define MP4_READ_U16(ctx,n) (size -= 2, READ_U16(ctx,n)) +#define MP4_READ_U24(ctx,n) (size -= 3, READ_U24(ctx,n)) +#define MP4_READ_U32(ctx,n) (size -= 4, READ_U32(ctx,n)) +#define MP4_READ_U64(ctx,n) (size -= 8, READ_U64(ctx,n)) +#define MP4_READ_FOURCC(ctx,n) (size -= 4, READ_FOURCC(ctx,n)) +#define MP4_SKIP_FOURCC(ctx,n) (size -= 4, SKIP_FOURCC(ctx,n)) +#define MP4_READ_BYTES(ctx,buffer,sz) (size -= sz, READ_BYTES(ctx,buffer,sz)) +#define MP4_SKIP_BYTES(ctx,sz) (size -= sz, SKIP_BYTES(ctx,sz)) +#define MP4_SKIP_STRING(ctx,sz,n) (size -= sz, SKIP_STRING(ctx,sz,n)) + +/****************************************************************************** +Type definitions. +******************************************************************************/ +typedef struct +{ + VC_CONTAINER_STATUS_T status; + + int64_t duration; + int64_t pts; + int64_t dts; + + uint32_t sample; + int64_t offset; + unsigned int sample_offset; + unsigned int sample_size; + + uint32_t sample_duration; + uint32_t sample_duration_count; + int32_t sample_composition_offset; + uint32_t sample_composition_count; + + uint32_t next_sync_sample; + bool keyframe; + + uint32_t samples_per_chunk; + uint32_t chunks; + uint32_t samples_in_chunk; + + struct { + uint32_t entry; + } sample_table[MP4_SAMPLE_TABLE_NUM]; + +} MP4_READER_STATE_T; + +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + MP4_READER_STATE_T state; + + int64_t timescale; + uint8_t object_type_indication; + + uint32_t sample_size; + struct { + int64_t offset; + uint32_t entries; + uint32_t entry_size; + } sample_table[MP4_SAMPLE_TABLE_NUM]; + + uint32_t samples_batch_size; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + int64_t box_offset; + int box_level; + + MP4_BRAND_T brand; + + int64_t timescale; + + VC_CONTAINER_TRACK_T *tracks[MP4_TRACKS_MAX]; + unsigned int current_track; + + bool found_moov; + int64_t data_offset; + int64_t data_size; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Static functions within this file. +******************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box( VC_CONTAINER_T *p_ctx, int64_t size, MP4_BOX_TYPE_T parent_type ); +static VC_CONTAINER_STATUS_T mp4_read_boxes( VC_CONTAINER_T *p_ctx, int64_t size, MP4_BOX_TYPE_T type ); +static VC_CONTAINER_STATUS_T mp4_read_box_ftyp( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_moov( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_mvhd( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_trak( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_tkhd( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_mdia( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_mdhd( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_hdlr( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_minf( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_vmhd( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_smhd( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_dinf( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_dref( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_stbl( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_stsd( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_stts( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_ctts( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_stsc( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_stsz( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_stco( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_co64( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_stss( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_vide( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_soun( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_text( VC_CONTAINER_T *p_ctx, int64_t size ); + +static VC_CONTAINER_STATUS_T mp4_read_box_esds( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_vide_avcC( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_vide_d263( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_soun_damr( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_soun_dawp( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_soun_devc( VC_CONTAINER_T *p_ctx, int64_t size ); +static VC_CONTAINER_STATUS_T mp4_read_box_soun_wave( VC_CONTAINER_T *p_ctx, int64_t size ); + +static struct { + const MP4_BOX_TYPE_T type; + VC_CONTAINER_STATUS_T (*pf_func)( VC_CONTAINER_T *, int64_t ); + const MP4_BOX_TYPE_T parent_type; +} mp4_box_list[] = +{ + {MP4_BOX_TYPE_FTYP, mp4_read_box_ftyp, MP4_BOX_TYPE_ROOT}, + {MP4_BOX_TYPE_MDAT, 0, MP4_BOX_TYPE_ROOT}, + {MP4_BOX_TYPE_MOOV, mp4_read_box_moov, MP4_BOX_TYPE_ROOT}, + {MP4_BOX_TYPE_MVHD, mp4_read_box_mvhd, MP4_BOX_TYPE_MOOV}, + {MP4_BOX_TYPE_TRAK, mp4_read_box_trak, MP4_BOX_TYPE_MOOV}, + {MP4_BOX_TYPE_TKHD, mp4_read_box_tkhd, MP4_BOX_TYPE_TRAK}, + {MP4_BOX_TYPE_MDIA, mp4_read_box_mdia, MP4_BOX_TYPE_TRAK}, + {MP4_BOX_TYPE_MDHD, mp4_read_box_mdhd, MP4_BOX_TYPE_MDIA}, + {MP4_BOX_TYPE_HDLR, mp4_read_box_hdlr, MP4_BOX_TYPE_MDIA}, + {MP4_BOX_TYPE_MINF, mp4_read_box_minf, MP4_BOX_TYPE_MDIA}, + {MP4_BOX_TYPE_VMHD, mp4_read_box_vmhd, MP4_BOX_TYPE_MINF}, + {MP4_BOX_TYPE_SMHD, mp4_read_box_smhd, MP4_BOX_TYPE_MINF}, + {MP4_BOX_TYPE_DINF, mp4_read_box_dinf, MP4_BOX_TYPE_MINF}, + {MP4_BOX_TYPE_DREF, mp4_read_box_dref, MP4_BOX_TYPE_DINF}, + {MP4_BOX_TYPE_STBL, mp4_read_box_stbl, MP4_BOX_TYPE_MINF}, + {MP4_BOX_TYPE_STSD, mp4_read_box_stsd, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_STTS, mp4_read_box_stts, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_CTTS, mp4_read_box_ctts, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_STSC, mp4_read_box_stsc, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_STSZ, mp4_read_box_stsz, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_STCO, mp4_read_box_stco, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_CO64, mp4_read_box_co64, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_STSS, mp4_read_box_stss, MP4_BOX_TYPE_STBL}, + {MP4_BOX_TYPE_VIDE, mp4_read_box_vide, MP4_BOX_TYPE_STSD}, + {MP4_BOX_TYPE_SOUN, mp4_read_box_soun, MP4_BOX_TYPE_STSD}, + {MP4_BOX_TYPE_TEXT, mp4_read_box_text, MP4_BOX_TYPE_STSD}, + + /* Codec specific boxes */ + {MP4_BOX_TYPE_AVCC, mp4_read_box_vide_avcC, MP4_BOX_TYPE_VIDE}, + {MP4_BOX_TYPE_D263, mp4_read_box_vide_d263, MP4_BOX_TYPE_VIDE}, + {MP4_BOX_TYPE_ESDS, mp4_read_box_esds, MP4_BOX_TYPE_VIDE}, + {MP4_BOX_TYPE_DAMR, mp4_read_box_soun_damr, MP4_BOX_TYPE_SOUN}, + {MP4_BOX_TYPE_DAWP, mp4_read_box_soun_dawp, MP4_BOX_TYPE_SOUN}, + {MP4_BOX_TYPE_DEVC, mp4_read_box_soun_devc, MP4_BOX_TYPE_SOUN}, + {MP4_BOX_TYPE_WAVE, mp4_read_box_soun_wave, MP4_BOX_TYPE_SOUN}, + {MP4_BOX_TYPE_ESDS, mp4_read_box_esds, MP4_BOX_TYPE_SOUN}, + + {MP4_BOX_TYPE_UNKNOWN, 0, MP4_BOX_TYPE_UNKNOWN} +}; + +static struct { + const VC_CONTAINER_FOURCC_T type; + const VC_CONTAINER_FOURCC_T codec; + bool batch; +} mp4_codec_mapping[] = +{ + {VC_FOURCC('a','v','c','1'), VC_CONTAINER_CODEC_H264, 0}, + {VC_FOURCC('m','p','4','v'), VC_CONTAINER_CODEC_MP4V, 0}, + {VC_FOURCC('s','2','6','3'), VC_CONTAINER_CODEC_H263, 0}, + {VC_FOURCC('m','p','e','g'), VC_CONTAINER_CODEC_MP2V, 0}, + {VC_FOURCC('m','j','p','a'), VC_CONTAINER_CODEC_MJPEGA, 0}, + {VC_FOURCC('m','j','p','b'), VC_CONTAINER_CODEC_MJPEGB, 0}, + + {VC_FOURCC('j','p','e','g'), VC_CONTAINER_CODEC_JPEG, 0}, + + {VC_FOURCC('m','p','4','a'), VC_CONTAINER_CODEC_MP4A, 0}, + {VC_FOURCC('s','a','m','r'), VC_CONTAINER_CODEC_AMRNB, 0}, + {VC_FOURCC('s','a','w','b'), VC_CONTAINER_CODEC_AMRWB, 0}, + {VC_FOURCC('s','a','w','p'), VC_CONTAINER_CODEC_AMRWBP, 0}, + {VC_FOURCC('a','c','-','3'), VC_CONTAINER_CODEC_AC3, 0}, + {VC_FOURCC('e','c','-','3'), VC_CONTAINER_CODEC_EAC3, 0}, + {VC_FOURCC('s','e','v','c'), VC_CONTAINER_CODEC_EVRC, 0}, + {VC_FOURCC('e','v','r','c'), VC_CONTAINER_CODEC_EVRC, 0}, + {VC_FOURCC('s','q','c','p'), VC_CONTAINER_CODEC_QCELP, 0}, + {VC_FOURCC('a','l','a','w'), VC_CONTAINER_CODEC_ALAW, 1}, + {VC_FOURCC('u','l','a','w'), VC_CONTAINER_CODEC_MULAW, 1}, + {VC_FOURCC('t','w','o','s'), VC_CONTAINER_CODEC_PCM_SIGNED_BE, 1}, + {VC_FOURCC('s','o','w','t'), VC_CONTAINER_CODEC_PCM_SIGNED_LE, 1}, + + {0, 0}, +}; +static VC_CONTAINER_FOURCC_T mp4_box_type_to_codec(VC_CONTAINER_FOURCC_T type) +{ + int i; + for(i = 0; mp4_codec_mapping[i].type; i++ ) + if(mp4_codec_mapping[i].type == type) break; + return mp4_codec_mapping[i].codec; +} + +static bool codec_needs_batch_mode(VC_CONTAINER_FOURCC_T codec) +{ + int i; + for(i = 0; mp4_codec_mapping[i].codec; i++ ) + if(mp4_codec_mapping[i].codec == codec) break; + return mp4_codec_mapping[i].batch; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_header( VC_CONTAINER_T *p_ctx, int64_t size, + MP4_BOX_TYPE_T *box_type, int64_t *box_size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + int64_t offset = STREAM_POSITION(p_ctx); + + module->box_offset = offset; + + *box_size = _READ_U32(p_ctx); + *box_type = _READ_FOURCC(p_ctx); + if(!*box_type) return VC_CONTAINER_ERROR_CORRUPTED; + + if(*box_size == 1) *box_size = _READ_U64(p_ctx); + LOG_FORMAT(p_ctx, "- Box %4.4s, Size: %"PRIi64", Offset: %"PRIi64, + (const char *)box_type, *box_size, offset); + + /* Sanity check the box size */ + if(*box_size < 0 /* Shouldn't ever get that big */ || + /* Only the mdat box can really be massive */ + (*box_type != MP4_BOX_TYPE_MDAT && *box_size > MP4_MAX_BOX_SIZE)) + { + LOG_DEBUG(p_ctx, "box %4.4s has an invalid size (%"PRIi64")", + (const char *)box_type, *box_size); + return VC_CONTAINER_ERROR_CORRUPTED; + } + +#if 0 + /* It is valid for a box to have a zero size (i.e unknown) if it is the last one */ + if(*box_size == 0 && size >= 0) *box_size = size; + else if(*box_size == 0) *box_size = INT64_C(-1); +#else + if(*box_size <= 0) + { + LOG_DEBUG(p_ctx, "box %4.4s has an invalid size (%"PRIi64")", + (const char *)box_type, *box_size); + return VC_CONTAINER_ERROR_CORRUPTED; + } +#endif + + /* Sanity check box size against parent */ + if(size >= 0 && *box_size > size) + { + LOG_DEBUG(p_ctx, "box %4.4s is bigger than it should (%"PRIi64" > %"PRIi64")", + (const char *)box_type, *box_size, size); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + *box_size -= (STREAM_POSITION(p_ctx) - offset); + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_data( VC_CONTAINER_T *p_ctx, + MP4_BOX_TYPE_T box_type, int64_t box_size, MP4_BOX_TYPE_T parent_type ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + int64_t offset = STREAM_POSITION(p_ctx); + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + unsigned int i; + + /* Check if the box is a recognised one */ + for(i = 0; mp4_box_list[i].type; i++) + if(mp4_box_list[i].type == box_type && + mp4_box_list[i].parent_type == parent_type) break; + if(mp4_box_list[i].type == MP4_BOX_TYPE_UNKNOWN) + for(i = 0; mp4_box_list[i].type; i++) + if(mp4_box_list[i].type == box_type) break; + + /* Sanity check that the box has the right parent */ + if(mp4_box_list[i].type != MP4_BOX_TYPE_UNKNOWN && + mp4_box_list[i].parent_type != MP4_BOX_TYPE_UNKNOWN && + parent_type != MP4_BOX_TYPE_UNKNOWN && parent_type != mp4_box_list[i].parent_type) + { + LOG_FORMAT(p_ctx, "Ignoring mis-placed box %4.4s", (const char *)&box_type); + goto skip; + } + + /* Sanity check that the element isn't too deeply nested */ + if(module->box_level >= 2 * MP4_MAX_BOX_LEVEL) + { + LOG_DEBUG(p_ctx, "box %4.4s is too deep. skipping", (const char *)&box_type); + goto skip; + } + + module->box_level++; + + /* Call the box specific parsing function */ + if(mp4_box_list[i].pf_func) + status = mp4_box_list[i].pf_func(p_ctx, box_size); + + module->box_level--; + + if(status != VC_CONTAINER_SUCCESS) + LOG_DEBUG(p_ctx, "box %4.4s appears to be corrupted (%i)", (const char *)&box_type, status); + + skip: + /* Skip the rest of the box */ + box_size -= (STREAM_POSITION(p_ctx) - offset); + if(box_size < 0) /* Check for overruns */ + { + /* Things have gone really bad here and we ended up reading past the end of the + * box. We could maybe try to be clever and recover by seeking back to the end + * of the box. However if we get there, the file is clearly corrupted so there's + * no guarantee it would work anyway. */ + LOG_DEBUG(p_ctx, "%"PRIi64" bytes overrun past the end of box %4.4s", + -box_size, (const char *)&box_type); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + if(box_size) + LOG_FORMAT(p_ctx, "%"PRIi64" bytes left unread in box %4.4s", + box_size, (const char *)&box_type ); + + if(box_size < MP4_MAX_BOX_SIZE) box_size = SKIP_BYTES(p_ctx, box_size); + else SEEK(p_ctx, STREAM_POSITION(p_ctx) + box_size); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box( VC_CONTAINER_T *p_ctx, int64_t size, + MP4_BOX_TYPE_T parent_type ) +{ + VC_CONTAINER_STATUS_T status; + MP4_BOX_TYPE_T box_type; + int64_t box_size; + + status = mp4_read_box_header( p_ctx, size, &box_type, &box_size ); + if(status != VC_CONTAINER_SUCCESS) return status; + return mp4_read_box_data( p_ctx, box_type, box_size, parent_type ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_boxes( VC_CONTAINER_T *p_ctx, int64_t size, + MP4_BOX_TYPE_T type) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t offset = STREAM_POSITION(p_ctx); + bool unknown_size = size < 0; + + /* Read contained boxes */ + module->box_level++; + while(status == VC_CONTAINER_SUCCESS && + (unknown_size || size >= MP4_BOX_MIN_HEADER_SIZE)) + { + offset = STREAM_POSITION(p_ctx); + status = mp4_read_box(p_ctx, size, type); + if(!unknown_size) size -= (STREAM_POSITION(p_ctx) - offset); + } + module->box_level--; + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_ftyp( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + module->brand = MP4_READ_FOURCC(p_ctx, "major_brand"); + MP4_SKIP_U32(p_ctx, "minor_version"); + while(size >= 4) MP4_SKIP_FOURCC(p_ctx, "compatible_brands"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_moov( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_MOOV); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_mvhd( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t version, i; + int64_t duration; + + version = MP4_READ_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + if(version) + { + MP4_SKIP_U64(p_ctx, "creation_time"); + MP4_SKIP_U64(p_ctx, "modification_time"); + module->timescale = MP4_READ_U32(p_ctx, "timescale"); + duration = MP4_READ_U64(p_ctx, "duration"); + } + else + { + MP4_SKIP_U32(p_ctx, "creation_time"); + MP4_SKIP_U32(p_ctx, "modification_time"); + module->timescale = MP4_READ_U32(p_ctx, "timescale"); + duration = MP4_READ_U32(p_ctx, "duration"); + } + + if(module->timescale) + p_ctx->duration = duration * 1000000 / module->timescale; + + MP4_SKIP_U32(p_ctx, "rate"); + MP4_SKIP_U16(p_ctx, "volume"); + MP4_SKIP_U16(p_ctx, "reserved"); + for(i = 0; i < 2; i++) MP4_SKIP_U32(p_ctx, "reserved"); + for(i = 0; i < 9; i++) MP4_SKIP_U32(p_ctx, "matrix"); + for(i = 0; i < 6; i++) MP4_SKIP_U32(p_ctx, "pre_defined"); + MP4_SKIP_U32(p_ctx, "next_track_ID"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_trak( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track; + + /* We have a new track. Allocate and initialise our track context */ + if(p_ctx->tracks_num >= MP4_TRACKS_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + p_ctx->tracks[p_ctx->tracks_num] = track = + vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STTS].entry_size = 8; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSZ].entry_size = 4; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSC].entry_size = 12; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STCO].entry_size = 4; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSS].entry_size = 4; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_CO64].entry_size = 8; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_CTTS].entry_size = 8; + + status = mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_TRAK); + + /* TODO: Sanity check track */ + + track->is_enabled = true; + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + module->current_track++; + p_ctx->tracks_num++; + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_tkhd( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t i, version; + int64_t duration; + + version = MP4_READ_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + if(version) + { + MP4_SKIP_U64(p_ctx, "creation_time"); + MP4_SKIP_U64(p_ctx, "modification_time"); + MP4_SKIP_U32(p_ctx, "track_ID"); + MP4_SKIP_U32(p_ctx, "reserved"); + duration = MP4_READ_U64(p_ctx, "duration"); + } + else + { + MP4_SKIP_U32(p_ctx, "creation_time"); + MP4_SKIP_U32(p_ctx, "modification_time"); + MP4_SKIP_U32(p_ctx, "track_ID"); + MP4_SKIP_U32(p_ctx, "reserved"); + duration = MP4_READ_U32(p_ctx, "duration"); + } + + if(module->timescale) + duration = duration * 1000000 / module->timescale; + + for(i = 0; i < 2; i++) MP4_SKIP_U32(p_ctx, "reserved"); + MP4_SKIP_U16(p_ctx, "layer"); + MP4_SKIP_U16(p_ctx, "alternate_group"); + MP4_SKIP_U16(p_ctx, "volume"); + MP4_SKIP_U16(p_ctx, "reserved"); + for(i = 0; i < 9; i++) MP4_SKIP_U32(p_ctx, "matrix"); + + MP4_SKIP_U32(p_ctx, "width"); + MP4_SKIP_U32(p_ctx, "height"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_mdia( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_MDIA); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_mdhd( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + uint32_t version, timescale; + int64_t duration; + + version = MP4_READ_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + if(version) + { + MP4_SKIP_U64(p_ctx, "creation_time"); + MP4_SKIP_U64(p_ctx, "modification_time"); + timescale = MP4_READ_U32(p_ctx, "timescale"); + duration = MP4_READ_U64(p_ctx, "duration"); + } + else + { + MP4_SKIP_U32(p_ctx, "creation_time"); + MP4_SKIP_U32(p_ctx, "modification_time"); + timescale = MP4_READ_U32(p_ctx, "timescale"); + duration = MP4_READ_U32(p_ctx, "duration"); + } + + if(timescale) duration = duration * 1000000 / timescale; + track_module->timescale = timescale; + + MP4_SKIP_U16(p_ctx, "language"); /* ISO-639-2/T language code */ + MP4_SKIP_U16(p_ctx, "pre_defined"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_hdlr( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + uint32_t i, fourcc, string_size; + VC_CONTAINER_ES_TYPE_T es_type = VC_CONTAINER_ES_TYPE_UNKNOWN; + + if(size <= 24) return VC_CONTAINER_ERROR_CORRUPTED; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + MP4_SKIP_U32(p_ctx, "pre-defined"); + + fourcc = MP4_READ_FOURCC(p_ctx, "handler_type"); + if(fourcc == MP4_BOX_TYPE_VIDE) es_type = VC_CONTAINER_ES_TYPE_VIDEO; + if(fourcc == MP4_BOX_TYPE_SOUN) es_type = VC_CONTAINER_ES_TYPE_AUDIO; + if(fourcc == MP4_BOX_TYPE_TEXT) es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE; + track->format->es_type = es_type; + + for(i = 0; i < 3; i++) MP4_SKIP_U32(p_ctx, "reserved"); + + string_size = size; + if(module->brand == MP4_BRAND_QT) + string_size = MP4_READ_U8(p_ctx, "string_size"); + + if(size < 0) return VC_CONTAINER_ERROR_CORRUPTED; + if(string_size > size) string_size = size; + + MP4_SKIP_STRING(p_ctx, string_size, "name"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_minf( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_MINF); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_vmhd( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + MP4_SKIP_U16(p_ctx, "graphicsmode"); + MP4_SKIP_U16(p_ctx, "opcolor"); + MP4_SKIP_U16(p_ctx, "opcolor"); + MP4_SKIP_U16(p_ctx, "opcolor"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_smhd( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + MP4_SKIP_U16(p_ctx, "balance"); + MP4_SKIP_U16(p_ctx, "reserved"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_dinf( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_DINF); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_dref( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + MP4_SKIP_U32(p_ctx, "entry_count"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_stbl( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_STBL); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_stsd( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + VC_CONTAINER_STATUS_T status; + MP4_BOX_TYPE_T box_type; + int64_t box_size; + uint32_t count; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + count = MP4_READ_U32(p_ctx, "entry_count"); + if(!count) return VC_CONTAINER_ERROR_CORRUPTED; + + status = mp4_read_box_header( p_ctx, size, &box_type, &box_size ); + if(status != VC_CONTAINER_SUCCESS) return status; + + track->format->codec = mp4_box_type_to_codec(box_type); + if(!track->format->codec) track->format->codec = box_type; + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) box_type = MP4_BOX_TYPE_VIDE; + if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) box_type = MP4_BOX_TYPE_SOUN; + if(track->format->es_type == VC_CONTAINER_ES_TYPE_SUBPICTURE) box_type = MP4_BOX_TYPE_TEXT; + status = mp4_read_box_data( p_ctx, box_type, box_size, MP4_BOX_TYPE_STSD ); + if(status != VC_CONTAINER_SUCCESS) return status; + + /* Special treatment for MPEG4 */ + if(track->format->codec == VC_CONTAINER_CODEC_MP4A) + { + switch (track->priv->module->object_type_indication) + { + case MP4_MPEG4_AAC_LC_OBJECT_TYPE: + case MP4_MPEG2_AAC_LC_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_MP4A; break; + case MP4_MP3_OBJECT_TYPE: + case MP4_MPEG1_AUDIO_OBJECT_TYPE: + case MP4_KTF_MP3_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_MPGA; break; + case MP4_SKT_EVRC_2V1_OBJECT_TYPE: + case MP4_SKT_EVRC_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_EVRC; break; + case MP4_3GPP2_QCELP_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_QCELP; break; + default: + track->format->codec = VC_CONTAINER_CODEC_UNKNOWN; break; + } + } + else if(track->format->codec == VC_CONTAINER_CODEC_MP4V) + { + switch (track->priv->module->object_type_indication) + { + case MP4_MPEG4_VISUAL_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_MP4V; break; + case MP4_JPEG_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_JPEG; break; + case MP4_MPEG2_SP_OBJECT_TYPE: + case MP4_MPEG2_SNR_OBJECT_TYPE: + case MP4_MPEG2_AAC_LC_OBJECT_TYPE: + case MP4_MPEG2_MP_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_MP2V; break; + case MP4_MPEG1_VISUAL_OBJECT_TYPE: + track->format->codec = VC_CONTAINER_CODEC_MP1V; break; + default: + track->format->codec = VC_CONTAINER_CODEC_UNKNOWN; break; + } + } + + /* For some codecs we process the samples in batches to be more efficient */ + if(codec_needs_batch_mode(track->format->codec)) + track->priv->module->samples_batch_size = MP4_MAX_SAMPLES_BATCH_SIZE; + + /* Fix-up some of the data */ + switch(track->format->codec) + { + case VC_CONTAINER_CODEC_ALAW: + case VC_CONTAINER_CODEC_MULAW: + track->format->type->audio.bits_per_sample = 8; + track->priv->module->sample_size = track->format->type->audio.channels; + break; + case VC_CONTAINER_CODEC_PCM_SIGNED_LE: + case VC_CONTAINER_CODEC_PCM_SIGNED_BE: + track->priv->module->sample_size = (track->format->type->audio.bits_per_sample + 7) / + 8 * track->format->type->audio.channels; + break; + case VC_CONTAINER_CODEC_MP4A: + /* samplerate / channels is sometimes invalid so sanity check it using the codec config data */ + if(track->format->extradata_size >= 2) + { + static unsigned int rate[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350 }; + unsigned int samplerate = 0, channels = 0; + uint8_t *p = track->format->extradata; + uint32_t index = (p[0] & 7) << 1 | (p[1] >> 7); + if(index == 15 && track->format->extradata_size >= 5) + { + samplerate = (p[1] & 0x7f) << 17 | (p[2] << 9) | (p[3] << 1) | (p[4] >> 7); + channels = (p[4] >> 3) & 15; + } + else if(index < 13) + { + samplerate = rate[index]; + channels = (p[1] >> 3) & 15;; + } + + if(samplerate && samplerate != track->format->type->audio.sample_rate && + 2 * samplerate != track->format->type->audio.sample_rate) + track->format->type->audio.sample_rate = samplerate; + if(channels && channels != track->format->type->audio.channels && + 2 * channels != track->format->type->audio.channels) + track->format->type->audio.channels = channels; + } + break; + default: break; + } + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_cache_table( VC_CONTAINER_T *p_ctx, MP4_SAMPLE_TABLE_T table, + uint32_t entries, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + uint32_t available_entries, entries_size; + + if(size < 0) return VC_CONTAINER_ERROR_CORRUPTED; + + track_module->sample_table[table].offset = STREAM_POSITION(p_ctx); + track_module->sample_table[table].entries = entries; + + available_entries = size / track_module->sample_table[table].entry_size; + if(available_entries < entries) + { + LOG_DEBUG(p_ctx, "table has less entries than advertised (%i/%i)", available_entries, entries); + entries = available_entries; + } + + entries_size = entries * track_module->sample_table[table].entry_size; + size = vc_container_io_cache(p_ctx->priv->io, entries_size ); + if(size != entries_size) + { + available_entries = size / track_module->sample_table[table].entry_size; + LOG_DEBUG(p_ctx, "cached less table entries than advertised (%i/%i)", available_entries, entries); + track_module->sample_table[table].entries = available_entries; + } + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_stts( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t entries; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + entries = MP4_READ_U32(p_ctx, "entry_count"); + return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STTS, entries, size ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_ctts( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t entries; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + entries = MP4_READ_U32(p_ctx, "entry_count"); + return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_CTTS, entries, size ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_stsc( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t entries; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + entries = MP4_READ_U32(p_ctx, "entry_count"); + return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STSC, entries, size ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_stsz( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + uint32_t entries; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + track_module->sample_size = READ_U32(p_ctx, "sample_size"); + if(track_module->sample_size) return STREAM_STATUS(p_ctx); + + entries = MP4_READ_U32(p_ctx, "sample_count"); + return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STSZ, entries, size ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_stco( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t entries; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + entries = MP4_READ_U32(p_ctx, "entry_count"); + return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STCO, entries, size ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_co64( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t entries; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + entries = MP4_READ_U32(p_ctx, "entry_count"); + return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_CO64, entries, size ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_stss( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + uint32_t entries; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + entries = MP4_READ_U32(p_ctx, "entry_count"); + return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STSS, entries, size ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_esds_descriptor_header(VC_CONTAINER_T *p_ctx, int64_t *size, + uint32_t *descriptor_length, uint8_t *descriptor_type) +{ + uint32_t byte, length = 0; + + if(*size <= 0) return VC_CONTAINER_ERROR_CORRUPTED; + + *descriptor_type = _READ_U8(p_ctx); + (*size)--; + + /* Read descriptor size */ + while(*size) + { + byte = _READ_U8(p_ctx); + (*size)--; + length = (length << 7) | (byte&0x7F); + if(!(byte & 0x80)) break; + } + + if(*size <= 0 || length > *size) + { + LOG_FORMAT(p_ctx, "esds descriptor is corrupted"); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + *descriptor_length = length; + LOG_FORMAT(p_ctx, "esds descriptor %x, size %i", *descriptor_type, *descriptor_length); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_esds( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + VC_CONTAINER_STATUS_T status; + uint32_t descriptor_length; + uint8_t descriptor_type; + + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U24(p_ctx, "flags"); + + status = mp4_read_esds_descriptor_header(p_ctx, &size, &descriptor_length, &descriptor_type); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(descriptor_type == 0x3) /* ES descriptor */ + { + uint8_t flags; + + MP4_SKIP_U16(p_ctx, "es_id"); + flags = MP4_READ_U8(p_ctx, "flags"); + + if(flags & 0x80) /* Stream dependence */ + MP4_SKIP_U16(p_ctx, "depend_on_es_id"); + + if(flags & 0x40) /* URL */ + { + uint32_t url_size = MP4_READ_U8(p_ctx, "url_size"); + MP4_SKIP_STRING(p_ctx, url_size, "url"); + } + + if(flags & 0x20) /* OCR_stream*/ + MP4_SKIP_U16(p_ctx, "OCR_es_id"); + + status = mp4_read_esds_descriptor_header(p_ctx, &size, &descriptor_length, &descriptor_type); + if(status != VC_CONTAINER_SUCCESS) return status; + } + + if(descriptor_type == 0x4) /* Decoder Config descriptor */ + { + track->priv->module->object_type_indication = MP4_READ_U8(p_ctx, "object_type_indication"); + MP4_SKIP_U8(p_ctx, "stream_type"); + MP4_SKIP_U24(p_ctx, "buffer_size_db"); + MP4_SKIP_U32(p_ctx, "max_bitrate"); + track->format->bitrate = MP4_READ_U32(p_ctx, "avg_bitrate"); + + if(size <= 0 || descriptor_length <= 13) return STREAM_STATUS(p_ctx); + + status = mp4_read_esds_descriptor_header(p_ctx, &size, &descriptor_length, &descriptor_type); + if(status != VC_CONTAINER_SUCCESS) return status; + if(descriptor_type == 0x05 && descriptor_length) + { + status = vc_container_track_allocate_extradata(p_ctx, track, descriptor_length); + if(status != VC_CONTAINER_SUCCESS) return status; + track->format->extradata_size = MP4_READ_BYTES(p_ctx, track->format->extradata, descriptor_length); + } + } + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_vide( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + unsigned int i; + + for(i = 0; i < 6; i++) MP4_SKIP_U8(p_ctx, "reserved"); + MP4_SKIP_U16(p_ctx, "data_reference_index"); + + MP4_SKIP_U16(p_ctx, "pre_defined"); + MP4_SKIP_U16(p_ctx, "reserved"); + for(i = 0; i < 3; i++) MP4_SKIP_U32(p_ctx, "pre_defined"); + track->format->type->video.width = MP4_READ_U16(p_ctx, "width"); + track->format->type->video.height = MP4_READ_U16(p_ctx, "height"); + MP4_SKIP_U32(p_ctx, "horizresolution"); /* dpi */ + MP4_SKIP_U32(p_ctx, "vertresolution"); /* dpi */ + MP4_SKIP_U32(p_ctx, "reserved"); + MP4_SKIP_U16(p_ctx, "frame_count"); + MP4_SKIP_BYTES(p_ctx, 32); + MP4_SKIP_U16(p_ctx, "depth"); + MP4_SKIP_U16(p_ctx, "pre_defined"); + + if(size > 0) + return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_VIDE ); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_vide_avcC( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + VC_CONTAINER_STATUS_T status; + + if(track->format->codec != VC_CONTAINER_CODEC_H264 || size <= 0) + return VC_CONTAINER_ERROR_CORRUPTED; + + track->format->codec_variant = VC_FOURCC('a','v','c','C'); + + status = vc_container_track_allocate_extradata(p_ctx, track, (unsigned int)size); + if(status != VC_CONTAINER_SUCCESS) return status; + track->format->extradata_size = READ_BYTES(p_ctx, track->format->extradata, size); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_vide_d263( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + MP4_SKIP_FOURCC(p_ctx, "vendor"); + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U8(p_ctx, "level"); + MP4_SKIP_U8(p_ctx, "profile"); + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_soun( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + unsigned int i, version = 0; + + for(i = 0; i < 6; i++) MP4_SKIP_U8(p_ctx, "reserved"); + MP4_SKIP_U16(p_ctx, "data_reference_index"); + + version = MP4_READ_U16(p_ctx, "version"); + MP4_SKIP_U16(p_ctx, "revision_level"); + MP4_SKIP_U32(p_ctx, "vendor"); + + track->format->type->audio.channels = MP4_READ_U16(p_ctx, "channelcount"); + track->format->type->audio.bits_per_sample = MP4_READ_U16(p_ctx, "samplesize"); + MP4_SKIP_U16(p_ctx, "pre_defined"); + MP4_SKIP_U16(p_ctx, "reserved"); + track->format->type->audio.sample_rate = MP4_READ_U16(p_ctx, "samplerate"); + MP4_SKIP_U16(p_ctx, "samplerate_fp_low"); + + if(version == 1) + { + MP4_SKIP_U32(p_ctx, "samples_per_packet"); + MP4_SKIP_U32(p_ctx, "bytes_per_packet"); + MP4_SKIP_U32(p_ctx, "bytes_per_frame"); + MP4_SKIP_U32(p_ctx, "bytes_per_sample"); + } + + if(size > 0) + return mp4_read_box( p_ctx, size, MP4_BOX_TYPE_SOUN ); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_soun_damr( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + + MP4_SKIP_FOURCC(p_ctx, "vendor"); + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U8(p_ctx, "mode_set"); + MP4_SKIP_U8(p_ctx, "mode_change_period"); + MP4_SKIP_U8(p_ctx, "frame_per_second"); + + track->format->type->audio.channels = 1; + if(track->format->codec == VC_CONTAINER_CODEC_AMRNB) + track->format->type->audio.sample_rate = 8000; + else if(track->format->codec == VC_CONTAINER_CODEC_AMRWB) + track->format->type->audio.sample_rate = 16000; + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_soun_dawp( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + + MP4_SKIP_FOURCC(p_ctx, "vendor"); + MP4_SKIP_U8(p_ctx, "version"); + + track->format->type->audio.channels = 2; + track->format->type->audio.sample_rate = 16000; + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_soun_devc( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + + MP4_SKIP_FOURCC(p_ctx, "vendor"); + MP4_SKIP_U8(p_ctx, "version"); + MP4_SKIP_U8(p_ctx, "samples_per_frame"); + + track->format->type->audio.channels = 1; + track->format->type->audio.sample_rate = 8000; + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_soun_wave( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_SOUN); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_box_text( VC_CONTAINER_T *p_ctx, int64_t size ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_PARAM_UNUSED(module); + + /* TODO */if(1) return VC_CONTAINER_ERROR_FAILED; + + if(size > 0) + return mp4_read_box( p_ctx, size, MP4_BOX_TYPE_TEXT ); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + + for(i = 0; i < p_ctx->tracks_num; i++) + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +#ifdef ENABLE_MP4_READER_LOG_STATE +static void mp4_log_state( VC_CONTAINER_T *p_ctx, MP4_READER_STATE_T *state ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + LOG_DEBUG(p_ctx, "state:"); + LOG_DEBUG(p_ctx, "duration: %i, pts %i, dts %i", (int)state->duration, + (int)state->pts, (int)state->dts); + LOG_DEBUG(p_ctx, "sample: %i, offset %i, sample_offset %i, sample_size %i", + state->sample, (int)state->offset, state->sample_offset, + state->sample_size); + LOG_DEBUG(p_ctx, "sample_duration: %i, count %i", + state->sample_duration, state->sample_duration_count); + LOG_DEBUG(p_ctx, "sample_composition_offset: %i, count %i", + state->sample_composition_offset, state->sample_composition_count); + LOG_DEBUG(p_ctx, "next_sync_sample: %i, keyframe %i", + state->next_sync_sample, state->keyframe); + LOG_DEBUG(p_ctx, "samples_per_chunk: %i, chunks %i, samples_in_chunk %i", + state->samples_per_chunk, state->chunks, state->samples_in_chunk); + LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STTS %i", state->sample_table[MP4_SAMPLE_TABLE_STTS].entry); + LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STSZ %i", state->sample_table[MP4_SAMPLE_TABLE_STSZ].entry); + LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STSC %i", state->sample_table[MP4_SAMPLE_TABLE_STSC].entry); + LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STCO %i", state->sample_table[MP4_SAMPLE_TABLE_STCO].entry); + LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_CO64 %i", state->sample_table[MP4_SAMPLE_TABLE_CO64].entry); + LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_CTTS %i", state->sample_table[MP4_SAMPLE_TABLE_CTTS].entry); +} +#endif /* ENABLE_MP4_READER_LOG_STATE */ + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_seek_sample_table( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *track_module, MP4_READER_STATE_T *state, + MP4_SAMPLE_TABLE_T table) +{ + int64_t seek_offset; + + /* Seek to the next entry in the table */ + if(state->sample_table[table].entry >= track_module->sample_table[table].entries) + return VC_CONTAINER_ERROR_EOS; + + seek_offset = track_module->sample_table[table].offset + + track_module->sample_table[table].entry_size * state->sample_table[table].entry; + + return SEEK(p_ctx, seek_offset); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_sample_table( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *track_module, MP4_READER_STATE_T *state, + MP4_SAMPLE_TABLE_T table, unsigned int seek) +{ + uint32_t value; + + if(table == MP4_SAMPLE_TABLE_STSZ && track_module->sample_size) + { + state->sample_size = track_module->sample_size; + return state->status; + } + + /* CO64 support */ + if(table == MP4_SAMPLE_TABLE_STCO && + track_module->sample_table[MP4_SAMPLE_TABLE_CO64].entries) + table = MP4_SAMPLE_TABLE_CO64; + + /* Seek to the next entry in the table */ + if(seek) + { + state->status = mp4_seek_sample_table( p_ctx, track_module, state, table ); + if(state->status != VC_CONTAINER_SUCCESS) return state->status; + } + + switch(table) + { + case MP4_SAMPLE_TABLE_STSZ: + state->sample_size = _READ_U32(p_ctx); + state->status = STREAM_STATUS(p_ctx); + break; + + case MP4_SAMPLE_TABLE_STTS: + state->sample_duration_count = _READ_U32(p_ctx); + state->sample_duration = _READ_U32(p_ctx); + state->status = STREAM_STATUS(p_ctx); + if(state->status != VC_CONTAINER_SUCCESS) break; + if(!state->sample_duration_count) state->status = VC_CONTAINER_ERROR_CORRUPTED; + break; + + case MP4_SAMPLE_TABLE_CTTS: + state->sample_composition_count = _READ_U32(p_ctx); + state->sample_composition_offset = _READ_U32(p_ctx); /* Converted to signed */ + state->status = STREAM_STATUS(p_ctx); + if(state->status != VC_CONTAINER_SUCCESS) break; + if(!state->sample_composition_count) state->status = VC_CONTAINER_ERROR_CORRUPTED; + break; + + case MP4_SAMPLE_TABLE_STSC: + state->chunks = _READ_U32(p_ctx); + state->samples_per_chunk = _READ_U32(p_ctx); + _SKIP_U32(p_ctx); + state->status = STREAM_STATUS(p_ctx); + if(state->status != VC_CONTAINER_SUCCESS) break; + + if(state->sample_table[table].entry + 1 < + track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries) value = _READ_U32(p_ctx); + else value = -1; + + if(!state->chunks || !state->samples_per_chunk || state->chunks >= value ) + {state->status = VC_CONTAINER_ERROR_CORRUPTED; break;} + state->chunks = value - state->chunks; + state->samples_in_chunk = state->samples_per_chunk; + break; + + case MP4_SAMPLE_TABLE_STCO: + case MP4_SAMPLE_TABLE_CO64: + state->offset = table == MP4_SAMPLE_TABLE_STCO ? _READ_U32(p_ctx) : _READ_U64(p_ctx); + state->status = STREAM_STATUS(p_ctx); + if(state->status != VC_CONTAINER_SUCCESS) break; + if(!state->offset) state->status = VC_CONTAINER_ERROR_CORRUPTED; + state->samples_in_chunk = state->samples_per_chunk; + break; + + case MP4_SAMPLE_TABLE_STSS: + state->next_sync_sample = _READ_U32(p_ctx); + state->status = STREAM_STATUS(p_ctx); + break; + + default: break; + } + + state->sample_table[table].entry++; + return state->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_sample_header( VC_CONTAINER_T *p_ctx, uint32_t track, + MP4_READER_STATE_T *state ) +{ + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[track]->priv->module; + + if(state->status != VC_CONTAINER_SUCCESS) return state->status; + + if(state->sample_offset < state->sample_size) + return state->status; /* We still have data left from the current sample */ + + /* Switch to the next sample */ + state->offset += state->sample_size; + state->sample_offset = 0; + state->sample_size = 0; + state->sample++; + + if(!state->samples_in_chunk) + { + /* We're switching to the next chunk */ + if(!state->chunks) + { + /* Seek to the next entry in the STSC */ + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSC, 1 ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + } + + /* Get the offset of the new chunk */ + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STCO, 1 ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + + state->chunks--; + } + state->samples_in_chunk--; + + /* Get the new sample size */ + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSZ, 1 ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + + /* Get the timestamp */ + if(track_module->timescale) + state->pts = state->dts = state->duration * 1000000 / track_module->timescale; + if(!state->sample_duration_count) + { + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STTS, 1 ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + } + state->sample_duration_count--; + + /* Get the composition time */ + if(track_module->sample_table[MP4_SAMPLE_TABLE_CTTS].entries) + { + if(!state->sample_composition_count) + { + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_CTTS, 1 ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + } + if(track_module->timescale) + state->pts = (state->duration + state->sample_composition_offset) * 1000000 / track_module->timescale; + state->sample_composition_count--; + } + state->duration += state->sample_duration; + + /* Get the keyframe flag */ + if(state->sample_table[MP4_SAMPLE_TABLE_STSS].entry < + track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries && + !state->next_sync_sample) + { + mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSS, 1 ); + state->status = VC_CONTAINER_SUCCESS; /* This isn't a critical error */ + } + + state->keyframe = + track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries && + state->sample == state->next_sync_sample; + if(state->keyframe) + state->next_sync_sample = 0; + + /* Try to batch several samples together if requested. We'll always stop at the chunk boundary */ + if(track_module->samples_batch_size) + { + uint32_t size = state->sample_size; + while(state->samples_in_chunk && size < track_module->samples_batch_size) + { + if(mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSZ, 1 )) break; + + if(!state->sample_duration_count) + if(mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STTS, 1 )) break; + + state->sample_duration_count--; + state->duration += state->sample_duration; + + size += state->sample_size; + state->samples_in_chunk--; + state->sample++; + } + state->sample_size = size; + } + +#ifdef ENABLE_MP4_READER_LOG_STATE + mp4_log_state(p_ctx, state); +#endif + + error: + return state->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_read_sample_data( VC_CONTAINER_T *p_ctx, uint32_t track, + MP4_READER_STATE_T *state, uint8_t *data, unsigned int *data_size ) +{ + VC_CONTAINER_STATUS_T status; + unsigned int size = state->sample_size - state->sample_offset; + + if(state->status != VC_CONTAINER_SUCCESS) return state->status; + + if(data_size && *data_size < size) size = *data_size; + + if(data) + { + state->status = SEEK(p_ctx, state->offset + state->sample_offset); + if(state->status != VC_CONTAINER_SUCCESS) return state->status; + + size = READ_BYTES(p_ctx, data, size); + } + state->sample_offset += size; + + if(data_size) *data_size = size; + state->status = STREAM_STATUS(p_ctx); + if(state->status != VC_CONTAINER_SUCCESS) return state->status; + + status = state->status; + + /* Switch to the start of the next sample */ + if(state->sample_offset >= state->sample_size) + mp4_read_sample_header(p_ctx, track, state); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet, uint32_t flags ) +{ + VC_CONTAINER_TRACK_MODULE_T *track_module; + VC_CONTAINER_STATUS_T status; + MP4_READER_STATE_T *state; + uint32_t i, track; + unsigned int data_size; + uint8_t *data = 0; + int64_t offset; + + /* Select the track to read from. If no specific track is requested by the caller, this + * will be the track to which the next bit of data in the mdat belongs to */ + if(!(flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK)) + { + for(i = 0, track = 0, offset = -1; i < p_ctx->tracks_num; i++) + { + track_module = p_ctx->tracks[i]->priv->module; + + /* Ignore tracks which have no more readable data */ + if(track_module->state.status != VC_CONTAINER_SUCCESS) continue; + + if(offset >= 0 && track_module->state.offset >= offset) continue; + offset = track_module->state.offset; + track = i; + } + } + else track = packet->track; + + if(track >= p_ctx->tracks_num) return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + track_module = p_ctx->tracks[track]->priv->module; + state = &track_module->state; + + status = mp4_read_sample_header(p_ctx, track, state); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(!packet) /* Skip packet */ + return mp4_read_sample_data(p_ctx, track, state, 0, 0); + + packet->dts = state->dts; + packet->pts = state->pts; + packet->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END; + if(state->keyframe) packet->flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME; + if(!state->sample_offset) packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + packet->track = track; + packet->frame_size = state->sample_size; + packet->size = state->sample_size - state->sample_offset; + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + return mp4_read_sample_data(p_ctx, track, state, 0, 0); + else if((flags & VC_CONTAINER_READ_FLAG_INFO) || !packet->data) + return VC_CONTAINER_SUCCESS; + + data = packet->data; + data_size = packet->buffer_size; + + status = mp4_read_sample_data(p_ctx, track, state, data, &data_size); + if(status != VC_CONTAINER_SUCCESS) + { + /* FIXME */ + return status; + } + + packet->size = data_size; + if(state->sample_offset) //? + packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + + return status; +} + +/*****************************************************************************/ +static uint32_t mp4_find_sample( VC_CONTAINER_T *p_ctx, uint32_t track, + MP4_READER_STATE_T *state, int64_t seek_time, VC_CONTAINER_STATUS_T *p_status ) +{ + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[track]->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t sample = 0, sample_duration_count; + int64_t sample_duration, seek_time_up = seek_time + 1; + unsigned int i; + VC_CONTAINER_PARAM_UNUSED(state); + + seek_time = seek_time * track_module->timescale / 1000000; + /* We also need to check against the time rounded up to account for + * rounding errors in the timestamp (because of the timescale conversion) */ + seek_time_up = seek_time_up * track_module->timescale / 1000000; + + status = SEEK(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_STTS].offset); + if(status != VC_CONTAINER_SUCCESS) goto end; + + /* Find the sample which corresponds to the requested time */ + for(i = 0; i < track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries; i++) + { + sample_duration_count = _READ_U32(p_ctx); + sample_duration = _READ_U32(p_ctx); + status = STREAM_STATUS(p_ctx); + if(status != VC_CONTAINER_SUCCESS) break; + + if(sample_duration_count * sample_duration <= seek_time) + { + seek_time -= sample_duration_count * sample_duration; + seek_time_up -= sample_duration_count * sample_duration; + sample += sample_duration_count; + continue; + } + if(!sample_duration) break; + + seek_time /= sample_duration; + seek_time_up /= sample_duration; + sample += MAX(seek_time, seek_time_up); + break; + } + + end: + if(p_status) *p_status = status; + return sample; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_seek_track( VC_CONTAINER_T *p_ctx, uint32_t track, + MP4_READER_STATE_T *state, uint32_t sample ) +{ + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[track]->priv->module; + uint32_t chunk = 0, samples; + unsigned int i; + + memset(state, 0, sizeof(*state)); + + /* Find the right chunk */ + for(i = 0, samples = sample; i < track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries; i++) + { + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSC, 1 ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + + if(state->chunks * state->samples_per_chunk <= samples) + { + samples -= state->chunks * state->samples_per_chunk; + chunk += state->chunks; + continue; + } + + while(samples >= state->samples_per_chunk) + { + samples -= state->samples_per_chunk; + state->chunks--; + chunk++; + } + + state->chunks--; + break; + } + + /* Get the offset of the selected chunk */ + state->sample_table[MP4_SAMPLE_TABLE_STCO].entry = chunk; + state->sample_table[MP4_SAMPLE_TABLE_CO64].entry = chunk; + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STCO, 1 ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + + /* Find the sample offset within the chunk */ + state->sample_table[MP4_SAMPLE_TABLE_STSZ].entry = sample - samples; + for(i = 0; i < samples; i++) + { + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSZ, !i ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + state->offset += state->sample_size; + state->samples_in_chunk--; + } + + /* Get the timestamp */ + for(i = 0, samples = sample; i < track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries; i++) + { + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STTS, !i ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + + if(state->sample_duration_count <= samples) + { + samples -= state->sample_duration_count; + state->duration += state->sample_duration * state->sample_duration_count; + continue; + } + + state->sample_duration_count -= samples; + state->duration += samples * state->sample_duration; + break; + } + + /* Find the right place in the sample composition table */ + for(i = 0, samples = sample; i < track_module->sample_table[MP4_SAMPLE_TABLE_CTTS].entries; i++) + { + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_CTTS, !i ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + + if(state->sample_composition_count <= samples) + { + samples -= state->sample_composition_count; + continue; + } + + state->sample_composition_count -= samples; + break; + } + + /* Find the right place in the synchronisation table */ + for(i = 0; i < track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries; i++) + { + state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSS, !i ); + if(state->status != VC_CONTAINER_SUCCESS) goto error; + + if(state->next_sync_sample >= sample + 1) break; + } + + state->sample = sample; + state->sample_size = 0; + mp4_read_sample_header(p_ctx, track, state); + + error: + return state->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_reader_seek(VC_CONTAINER_T *p_ctx, + int64_t *offset, VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module; + VC_CONTAINER_STATUS_T status; + uint32_t i, track, sample, prev_sample, next_sample; + int64_t seek_time = *offset; + VC_CONTAINER_PARAM_UNUSED(module); + VC_CONTAINER_PARAM_UNUSED(mode); + + /* Reset the states */ + for(i = 0; i < p_ctx->tracks_num; i++) + memset(&p_ctx->tracks[i]->priv->module->state, 0, sizeof(p_ctx->tracks[i]->priv->module->state)); + + /* Deal with the easy case first */ + if(!*offset) + { + /* Initialise tracks */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + /* FIXME: we should check we've got at least one success */ + mp4_read_sample_header(p_ctx, i, &p_ctx->tracks[i]->priv->module->state); + } + return VC_CONTAINER_SUCCESS; + } + + /* Find the first enabled video track */ + for(track = 0; track < p_ctx->tracks_num; track++) + if(p_ctx->tracks[track]->is_enabled && + p_ctx->tracks[track]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) break; + if(track == p_ctx->tracks_num) goto seek_time_found; /* No video track found */ + track_module = p_ctx->tracks[track]->priv->module; + + /* Find the sample number for the requested time */ + sample = mp4_find_sample( p_ctx, track, &track_module->state, seek_time, &status ); + if(status != VC_CONTAINER_SUCCESS) goto seek_time_found; + + /* Find the closest sync sample */ + status = mp4_seek_sample_table( p_ctx, track_module, &track_module->state, MP4_SAMPLE_TABLE_STSS ); + if(status != VC_CONTAINER_SUCCESS) goto seek_time_found; + for(i = 0, prev_sample = 0, next_sample = 0; + i < track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries; i++) + { + next_sample = _READ_U32(p_ctx) - 1; + if(next_sample > sample) + { + sample = (flags & VC_CONTAINER_SEEK_FLAG_FORWARD) ? next_sample : prev_sample; + break; + } + prev_sample = next_sample; + } + + /* Do the seek on this track and use its timestamp as the new seek point */ + status = mp4_seek_track(p_ctx, track, &track_module->state, sample); + if(status != VC_CONTAINER_SUCCESS) goto seek_time_found; + seek_time = track_module->state.pts; + + seek_time_found: + + for(i = 0; i < p_ctx->tracks_num; i++) + { + uint32_t sample; + track_module = p_ctx->tracks[i]->priv->module; + if(track_module->state.offset) continue; + sample = mp4_find_sample( p_ctx, i, &track_module->state, seek_time, &status ); + if(status != VC_CONTAINER_SUCCESS) return status; //FIXME + + status = mp4_seek_track(p_ctx, i, &track_module->state, sample); + } + + *offset = seek_time; + return VC_CONTAINER_SUCCESS; +} + +/****************************************************************************** +Global function definitions. +******************************************************************************/ + +VC_CONTAINER_STATUS_T mp4_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + VC_CONTAINER_MODULE_T *module = 0; + unsigned int i; + uint8_t h[8]; + + /* Check for a known box type to see if we're dealing with mp4 */ + if( PEEK_BYTES(p_ctx, h, 8) != 8 ) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + switch(VC_FOURCC(h[4],h[5],h[6],h[7])) + { + case MP4_BOX_TYPE_FTYP: + case MP4_BOX_TYPE_MDAT: + case MP4_BOX_TYPE_MOOV: + case MP4_BOX_TYPE_FREE: + case MP4_BOX_TYPE_SKIP: + case MP4_BOX_TYPE_WIDE: + case MP4_BOX_TYPE_PNOT: + case MP4_BOX_TYPE_PICT: + case MP4_BOX_TYPE_UDTA: + case MP4_BOX_TYPE_UUID: + break; + default: + /* Couldn't recognize the box type. This doesn't look like an mp4. */ + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + /* + * We are dealing with an MP4 file + */ + + LOG_DEBUG(p_ctx, "using mp4 reader"); + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = module->tracks; + + while(STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS) + { + MP4_BOX_TYPE_T box_type; + int64_t box_size; + + status = mp4_read_box_header( p_ctx, INT64_C(-1), &box_type, &box_size ); + if(status != VC_CONTAINER_SUCCESS) goto error; + + if(box_type == MP4_BOX_TYPE_MDAT) + { + module->data_offset = STREAM_POSITION(p_ctx); + module->data_size = box_size; + if(module->found_moov) break; /* We've got everything we want */ + } + else if(box_type == MP4_BOX_TYPE_MOOV) + module->found_moov = true; + + status = mp4_read_box_data( p_ctx, box_type, box_size, MP4_BOX_TYPE_ROOT ); + if(status != VC_CONTAINER_SUCCESS) goto error; + + if(module->found_moov && module->data_offset) break; /* We've got everything we want */ + } + + /* Initialise tracks */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + /* FIXME: we should check we've got at least one success */ + status = mp4_read_sample_header(p_ctx, i, &p_ctx->tracks[i]->priv->module->state); + } + + status = SEEK(p_ctx, module->data_offset); + if(status != VC_CONTAINER_SUCCESS) goto error; + + p_ctx->priv->pf_close = mp4_reader_close; + p_ctx->priv->pf_read = mp4_reader_read; + p_ctx->priv->pf_seek = mp4_reader_seek; + + if(STREAM_SEEKABLE(p_ctx)) + p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "mp4: error opening stream"); + if(module) mp4_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open mp4_reader_open +#endif diff --git a/containers/mp4/mp4_writer.c b/containers/mp4/mp4_writer.c new file mode 100644 index 0000000..5b33e01 --- /dev/null +++ b/containers/mp4/mp4_writer.c @@ -0,0 +1,1441 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#define CONTAINER_IS_BIG_ENDIAN +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_writer_utils.h" +#include "containers/core/containers_logging.h" +#include "containers/mp4/mp4_common.h" +#undef CONTAINER_HELPER_LOG_INDENT +#define CONTAINER_HELPER_LOG_INDENT(a) (a)->priv->module->box_level + +VC_CONTAINER_STATUS_T mp4_writer_open( VC_CONTAINER_T *p_ctx ); + +/****************************************************************************** +Defines. +******************************************************************************/ +#define MP4_TRACKS_MAX 16 +#define MP4_TIMESCALE 1000 + +#define MP4_64BITS_TIME 0 /* 0 to disable / 1 to enable */ + +/****************************************************************************** +Type definitions. +******************************************************************************/ +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + uint32_t fourcc; + uint32_t samples; + uint32_t chunks; + + int64_t offset; + int64_t timestamp; + int64_t delta_timestamp; + int64_t samples_in_chunk; + int64_t samples_in_prev_chunk; + struct { + uint32_t entries; + uint32_t entry_size; + } sample_table[MP4_SAMPLE_TABLE_NUM]; + + int64_t first_pts; + int64_t last_pts; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + int box_level; + MP4_BRAND_T brand; + + VC_CONTAINER_TRACK_T *tracks[MP4_TRACKS_MAX]; + bool tracks_add_done; + + VC_CONTAINER_WRITER_EXTRAIO_T null; + + unsigned int current_track; + + unsigned moov_size; + int64_t mdat_offset; + int64_t data_offset; + + uint32_t samples; + VC_CONTAINER_WRITER_EXTRAIO_T temp; + VC_CONTAINER_PACKET_T sample; + int64_t sample_offset; + int64_t prev_sample_dts; + + int64_t duration; + /**/ + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Static functions within this file. +******************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_extended( VC_CONTAINER_T *p_ctx, MP4_BOX_TYPE_T box_type, uint32_t fourcc ); +static VC_CONTAINER_STATUS_T mp4_write_box( VC_CONTAINER_T *p_ctx, MP4_BOX_TYPE_T box_type ); +static VC_CONTAINER_STATUS_T mp4_write_box_ftyp( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_moov( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_mvhd( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_trak( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_tkhd( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_mdia( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_mdhd( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_hdlr( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_minf( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_vmhd( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_smhd( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_dinf( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_dref( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_stbl( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_stsd( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_stts( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_ctts( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_stsc( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_stsz( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_stco( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_co64( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_stss( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_vide( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_soun( VC_CONTAINER_T *p_ctx ); +static VC_CONTAINER_STATUS_T mp4_write_box_esds( VC_CONTAINER_T *p_ctx ); + +static struct { + const MP4_BOX_TYPE_T type; + VC_CONTAINER_STATUS_T (*pf_func)( VC_CONTAINER_T * ); +} mp4_box_list[] = +{ + {MP4_BOX_TYPE_FTYP, mp4_write_box_ftyp}, + {MP4_BOX_TYPE_MOOV, mp4_write_box_moov}, + {MP4_BOX_TYPE_MVHD, mp4_write_box_mvhd}, + {MP4_BOX_TYPE_TRAK, mp4_write_box_trak}, + {MP4_BOX_TYPE_TKHD, mp4_write_box_tkhd}, + {MP4_BOX_TYPE_MDIA, mp4_write_box_mdia}, + {MP4_BOX_TYPE_MDHD, mp4_write_box_mdhd}, + {MP4_BOX_TYPE_HDLR, mp4_write_box_hdlr}, + {MP4_BOX_TYPE_MINF, mp4_write_box_minf}, + {MP4_BOX_TYPE_VMHD, mp4_write_box_vmhd}, + {MP4_BOX_TYPE_SMHD, mp4_write_box_smhd}, + {MP4_BOX_TYPE_DINF, mp4_write_box_dinf}, + {MP4_BOX_TYPE_DREF, mp4_write_box_dref}, + {MP4_BOX_TYPE_STBL, mp4_write_box_stbl}, + {MP4_BOX_TYPE_STSD, mp4_write_box_stsd}, + {MP4_BOX_TYPE_STTS, mp4_write_box_stts}, + {MP4_BOX_TYPE_CTTS, mp4_write_box_ctts}, + {MP4_BOX_TYPE_STSC, mp4_write_box_stsc}, + {MP4_BOX_TYPE_STSZ, mp4_write_box_stsz}, + {MP4_BOX_TYPE_STCO, mp4_write_box_stco}, + {MP4_BOX_TYPE_CO64, mp4_write_box_co64}, + {MP4_BOX_TYPE_STSS, mp4_write_box_stss}, + {MP4_BOX_TYPE_VIDE, mp4_write_box_vide}, + {MP4_BOX_TYPE_SOUN, mp4_write_box_soun}, + {MP4_BOX_TYPE_ESDS, mp4_write_box_esds}, + {MP4_BOX_TYPE_UNKNOWN, 0} +}; + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_extended( VC_CONTAINER_T *p_ctx, MP4_BOX_TYPE_T type, uint32_t fourcc ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t box_size = 0; + unsigned int i; + + /* Find out which object we want to write */ + for( i = 0; mp4_box_list[i].type && mp4_box_list[i].type != type; i++ ); + + /* Check we found the requested type */ + if(!mp4_box_list[i].type) + { + vc_container_assert(0); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + /* We need to find out the size of the object we're going to write it. */ + if(!vc_container_writer_extraio_enable(p_ctx, &module->null)) + { + status = mp4_write_box_extended( p_ctx, type, fourcc ); + box_size = STREAM_POSITION(p_ctx); + } + vc_container_writer_extraio_disable(p_ctx, &module->null); + if(status != VC_CONTAINER_SUCCESS) return status; + + /* Write the object header */ + LOG_FORMAT(p_ctx, "- Box %4.4s, size: %"PRIi64, (const char *)&fourcc, box_size); + _WRITE_U32(p_ctx, (uint32_t)box_size); + _WRITE_FOURCC(p_ctx, fourcc); + + module->box_level++; + + /* Call the object specific writing function */ + status = mp4_box_list[i].pf_func(p_ctx); + + module->box_level--; + + if(status != VC_CONTAINER_SUCCESS) + LOG_DEBUG(p_ctx, "box %4.4s appears to be corrupted", (char *)mp4_box_list[i].type); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box( VC_CONTAINER_T *p_ctx, MP4_BOX_TYPE_T type ) +{ + return mp4_write_box_extended( p_ctx, type, type ); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_ftyp( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + WRITE_FOURCC(p_ctx, module->brand, "major_brand"); + WRITE_U32(p_ctx, 512, "minor_version"); + if(module->brand == MP4_BRAND_QT) + { + WRITE_FOURCC(p_ctx, MP4_BRAND_QT, "compatible_brands"); + return STREAM_STATUS(p_ctx); + } + + if(module->brand == MP4_BRAND_SKM2) + WRITE_FOURCC(p_ctx, MP4_BRAND_SKM2, "compatible_brands"); + WRITE_FOURCC(p_ctx, MP4_BRAND_ISOM, "compatible_brands"); + WRITE_FOURCC(p_ctx, MP4_BRAND_MP42, "compatible_brands"); + WRITE_FOURCC(p_ctx, MP4_BRAND_3GP4, "compatible_brands"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_moov( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_MVHD); + if(status != VC_CONTAINER_SUCCESS) return status; + + for(i = 0; i < p_ctx->tracks_num; i++) + { + module->current_track = i; + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_TRAK); + if(status != VC_CONTAINER_SUCCESS) return status; + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_mvhd( VC_CONTAINER_T *p_ctx ) +{ + static uint32_t matrix[] = { 0x10000,0,0,0,0x10000,0,0,0,0x40000000 }; + unsigned int version = MP4_64BITS_TIME; + unsigned int i; + + WRITE_U8(p_ctx, version, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + /**/ + p_ctx->duration = 0; + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[i]; + VC_CONTAINER_TRACK_MODULE_T *track_module = track->priv->module; + int64_t track_duration = track_module->last_pts - track_module->first_pts; + if(track_duration > p_ctx->duration) + p_ctx->duration = track_duration; + } + + if(version) + { + WRITE_U64(p_ctx, 0, "creation_time"); + WRITE_U64(p_ctx, 0, "modification_time"); + WRITE_U32(p_ctx, MP4_TIMESCALE, "timescale"); + WRITE_U64(p_ctx, p_ctx->duration * MP4_TIMESCALE / 1000000, "duration"); + } + else + { + WRITE_U32(p_ctx, 0, "creation_time"); + WRITE_U32(p_ctx, 0, "modification_time"); + WRITE_U32(p_ctx, MP4_TIMESCALE, "timescale"); + WRITE_U32(p_ctx, p_ctx->duration * MP4_TIMESCALE / 1000000, "duration"); + } + + WRITE_U32(p_ctx, 0x10000, "rate"); /* 1.0 */ + WRITE_U16(p_ctx, 0x100, "volume"); /* full volume */ + WRITE_U16(p_ctx, 0, "reserved"); + for(i = 0; i < 2; i++) + WRITE_U32(p_ctx, 0, "reserved"); + for(i = 0; i < 9; i++) /* unity matrix */ + WRITE_U32(p_ctx, matrix[i], "matrix"); + for(i = 0; i < 6; i++) + WRITE_U32(p_ctx, 0, "pre_defined"); + WRITE_U32(p_ctx, p_ctx->tracks_num + 1, "next_track_ID"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_trak( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_TKHD); + if(status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_MDIA); + if(status != VC_CONTAINER_SUCCESS) return status; + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_tkhd( VC_CONTAINER_T *p_ctx ) +{ + static uint32_t matrix[] = { 0x10000,0,0,0,0x10000,0,0,0,0x40000000 }; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + unsigned int version = MP4_64BITS_TIME; + uint32_t i, width = 0, height = 0; + + WRITE_U8(p_ctx, version, "version"); + WRITE_U24(p_ctx, 0x7, "flags"); /* track enabled */ + + if(version) + { + WRITE_U64(p_ctx, 0, "creation_time"); + WRITE_U64(p_ctx, 0, "modification_time"); + WRITE_U32(p_ctx, module->current_track + 1, "track_ID"); + WRITE_U32(p_ctx, 0, "reserved"); + WRITE_U64(p_ctx, p_ctx->duration * MP4_TIMESCALE / 1000000, "duration"); + } + else + { + WRITE_U32(p_ctx, 0, "creation_time"); + WRITE_U32(p_ctx, 0, "modification_time"); + WRITE_U32(p_ctx, module->current_track + 1, "track_ID"); + WRITE_U32(p_ctx, 0, "reserved"); + WRITE_U32(p_ctx, p_ctx->duration * MP4_TIMESCALE / 1000000, "duration"); + } + + for(i = 0; i < 2; i++) + WRITE_U32(p_ctx, 0, "reserved"); + WRITE_U16(p_ctx, 0, "layer"); + WRITE_U16(p_ctx, 0, "alternate_group"); + WRITE_U16(p_ctx, track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO ? 0x100 : 0, "volume"); + WRITE_U16(p_ctx, 0, "reserved"); + for(i = 0; i < 9; i++) /* unity matrix */ + WRITE_U32(p_ctx, matrix[i], "matrix"); + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + width = track->format->type->video.width << 16; + height = track->format->type->video.height << 16; + if(track->format->type->video.par_num && track->format->type->video.par_den) + width = width * (uint64_t)track->format->type->video.par_num / + track->format->type->video.par_den; + } + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_SUBPICTURE) + { + /* FIXME */ + } + + WRITE_U32(p_ctx, width, "width"); + WRITE_U32(p_ctx, height, "height"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_mdia( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_MDHD); + if(status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_HDLR); + if(status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_MINF); + if(status != VC_CONTAINER_SUCCESS) return status; + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_mdhd( VC_CONTAINER_T *p_ctx ) +{ + unsigned int version = MP4_64BITS_TIME; + + WRITE_U8(p_ctx, version, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + // FIXME: take a better timescale ?? + if(version) + { + WRITE_U64(p_ctx, 0, "creation_time"); + WRITE_U64(p_ctx, 0, "modification_time"); + WRITE_U32(p_ctx, MP4_TIMESCALE, "timescale"); + WRITE_U64(p_ctx, p_ctx->duration * MP4_TIMESCALE / 1000000, "duration"); + } + else + { + WRITE_U32(p_ctx, 0, "creation_time"); + WRITE_U32(p_ctx, 0, "modification_time"); + WRITE_U32(p_ctx, MP4_TIMESCALE, "timescale"); + WRITE_U32(p_ctx, p_ctx->duration * MP4_TIMESCALE / 1000000, "duration"); + } + + WRITE_U16(p_ctx, 0x55c4, "language"); /* ISO-639-2/T language code */ + WRITE_U16(p_ctx, 0, "pre_defined"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_hdlr( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + uint32_t i, handler_size, fourcc = 0; + const char *handler_name; + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) fourcc = VC_FOURCC('v','i','d','e'); + if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) fourcc = VC_FOURCC('s','o','u','n'); + if(track->format->es_type == VC_CONTAINER_ES_TYPE_SUBPICTURE) fourcc = VC_FOURCC('t','e','x','t'); + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + if(module->brand == MP4_BRAND_QT) + WRITE_FOURCC(p_ctx, VC_FOURCC('m','h','l','r'), "component_type"); + else + WRITE_U32(p_ctx, 0, "pre-defined"); + + WRITE_FOURCC(p_ctx, fourcc, "handler_type"); + for(i = 0; i < 3; i++) + WRITE_U32(p_ctx, 0, "reserved"); + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { handler_name = "Video Media Handler"; handler_size = sizeof("Video Media Handler"); } + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + { handler_name = "Audio Media Handler"; handler_size = sizeof("Audio Media Handler"); } + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_SUBPICTURE) + { handler_name = "Text Media Handler"; handler_size = sizeof("Text Media Handler"); } + else { handler_name = ""; handler_size = sizeof(""); } + + if(module->brand == MP4_BRAND_QT) + { handler_size--; WRITE_U8(p_ctx, handler_size, "string_size"); } + + WRITE_STRING(p_ctx, handler_name, handler_size, "name"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_minf( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_VMHD); + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_SMHD); +#if 0 + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_SUBPICTURE) + /*FIXME */; +#endif + if(status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_DINF); + if(status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_STBL); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_vmhd( VC_CONTAINER_T *p_ctx ) +{ + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 1, "flags"); + + WRITE_U16(p_ctx, 0, "graphicsmode"); + WRITE_U16(p_ctx, 0, "opcolor"); + WRITE_U16(p_ctx, 0, "opcolor"); + WRITE_U16(p_ctx, 0, "opcolor"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_smhd( VC_CONTAINER_T *p_ctx ) +{ + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + WRITE_U16(p_ctx, 0, "balance"); + WRITE_U16(p_ctx, 0, "reserved"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_dinf( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_DREF); + if(status != VC_CONTAINER_SUCCESS) return status; + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_dref( VC_CONTAINER_T *p_ctx ) +{ + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + WRITE_U32(p_ctx, 1, "entry_count"); + + /* Add a URL box */ + WRITE_U32(p_ctx, 12, "box_size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('u','r','l',' '), "box_type"); + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0x1, "flags"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_stbl( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_STSD); + if(status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_STTS); + if(status != VC_CONTAINER_SUCCESS) return status; + + if( 0 && track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_CTTS); + if(status != VC_CONTAINER_SUCCESS) return status; + } + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_STSC); + if(status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_STSZ); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(1) + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_STCO); + else + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_CO64); + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_STSS); + if(status != VC_CONTAINER_SUCCESS) return status; + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_stsd( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + WRITE_U32(p_ctx, 1, "entry_count"); + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + status = mp4_write_box_extended(p_ctx, MP4_BOX_TYPE_VIDE, track->priv->module->fourcc); + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + status = mp4_write_box_extended(p_ctx, MP4_BOX_TYPE_SOUN, track->priv->module->fourcc); +#if 0 + else if(track->format->es_type == VC_CONTAINER_ES_TYPE_SUBPICTURE) + /*FIXME*/; +#endif + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_write_sample_to_temp( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + int32_t dts_diff = packet->dts - module->prev_sample_dts; + uint8_t keyframe = (packet->flags & VC_CONTAINER_PACKET_FLAG_KEYFRAME) ? 0x80 : 0; + + vc_container_io_write_be_uint32(module->temp.io, packet->size); + vc_container_io_write_be_uint32(module->temp.io, dts_diff); + vc_container_io_write_be_uint24(module->temp.io, (uint32_t)(packet->pts - packet->dts)); + vc_container_io_write_uint8(module->temp.io, packet->track | keyframe); + module->prev_sample_dts = packet->dts; + return module->temp.io->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_read_sample_from_temp( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + packet->size = vc_container_io_read_be_uint32(module->temp.io); + packet->dts += (int32_t)vc_container_io_read_be_uint32(module->temp.io); + packet->pts = packet->dts + vc_container_io_read_be_uint24(module->temp.io); + packet->track = vc_container_io_read_uint8(module->temp.io); + packet->flags = (packet->track & 0x80) ? VC_CONTAINER_PACKET_FLAG_KEYFRAME : 0; + packet->track &= 0x7F; + return module->temp.io->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_stts( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_PACKET_T sample; + unsigned int entries = 0; + int64_t last_dts = 0, delta; + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + WRITE_U32(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries, "entry_count"); + + if(module->null.refcount) + { + /* We're not actually writing the data, we just want the size */ + WRITE_BYTES(p_ctx, 0, track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries * 8); + return STREAM_STATUS(p_ctx); + } + + /* Go through all the samples written */ + vc_container_io_seek(module->temp.io, INT64_C(0)); + sample.dts = 0; + + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + while(status == VC_CONTAINER_SUCCESS) + { + if(sample.track != module->current_track) goto skip; + + delta = sample.dts * MP4_TIMESCALE / 1000000 - last_dts; + if(delta < 0) delta = 0; + WRITE_U32(p_ctx, 1, "sample_count"); + WRITE_U32(p_ctx, delta, "sample_delta"); + entries++; + last_dts += delta; + + skip: + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + } + vc_container_assert(entries == track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_ctts( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + WRITE_U32(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_CTTS].entries, "entry_count"); + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_stsc( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_PACKET_T sample; + int64_t offset = 0, track_offset = -1; + unsigned int entries = 0, chunks = 0, first_chunk = 0, samples_in_chunk = 0; + + memset(&sample, 0, sizeof(VC_CONTAINER_PACKET_T)); + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + WRITE_U32(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries, "entry_count"); + + if(module->null.refcount) + { + /* We're not actually writing the data, we just want the size */ + WRITE_BYTES(p_ctx, 0, track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries * 12); + return STREAM_STATUS(p_ctx); + } + + /* Go through all the samples written */ + vc_container_io_seek(module->temp.io, INT64_C(0)); + + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + while(status == VC_CONTAINER_SUCCESS) + { + if(sample.track != module->current_track) goto skip; + + /* Is it a new chunk ? */ + if(track_offset != offset) + { + chunks++; + if(samples_in_chunk) + { + WRITE_U32(p_ctx, first_chunk, "first_chunk"); + WRITE_U32(p_ctx, samples_in_chunk, "samples_per_chunk"); + WRITE_U32(p_ctx, 1, "sample_description_index"); + entries++; + } + first_chunk = chunks; + samples_in_chunk = 0; + } + track_offset = offset + sample.size; + samples_in_chunk++; + + skip: + offset += sample.size; + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + } + + if(samples_in_chunk) + { + WRITE_U32(p_ctx, first_chunk, "first_chunk"); + WRITE_U32(p_ctx, samples_in_chunk, "samples_per_chunk"); + WRITE_U32(p_ctx, 1, "sample_description_index"); + entries++; + } + + vc_container_assert(entries == track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_stsz( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_PACKET_T sample; + unsigned int entries = 0; + + memset(&sample, 0, sizeof(VC_CONTAINER_PACKET_T)); + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + WRITE_U32(p_ctx, 0, "sample_size"); + WRITE_U32(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_STSZ].entries, "sample_count"); + + if(module->null.refcount) + { + /* We're not actually writing the data, we just want the size */ + WRITE_BYTES(p_ctx, 0, track_module->sample_table[MP4_SAMPLE_TABLE_STSZ].entries * 4); + return STREAM_STATUS(p_ctx); + } + + /* Go through all the samples written */ + vc_container_io_seek(module->temp.io, INT64_C(0)); + + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + while(status == VC_CONTAINER_SUCCESS) + { + if(sample.track != module->current_track) goto skip; + + WRITE_U32(p_ctx, sample.size, "entry_size"); + entries++; + + skip: + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + } + vc_container_assert(entries == track_module->sample_table[MP4_SAMPLE_TABLE_STSZ].entries); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_stco( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_PACKET_T sample; + int64_t offset = module->data_offset, track_offset = -1; + unsigned int entries = 0; + + memset(&sample, 0, sizeof(VC_CONTAINER_PACKET_T)); + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + WRITE_U32(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_STCO].entries, "entry_count"); + + if(module->null.refcount) + { + /* We're not actually writing the data, we just want the size */ + WRITE_BYTES(p_ctx, 0, track_module->sample_table[MP4_SAMPLE_TABLE_STCO].entries * 4); + return STREAM_STATUS(p_ctx); + } + + /* Go through all the samples written */ + vc_container_io_seek(module->temp.io, INT64_C(0)); + + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + while(status == VC_CONTAINER_SUCCESS) + { + if(sample.track != module->current_track) goto skip; + + /* Is it a new chunk ? */ + if(track_offset != offset) + { + WRITE_U32(p_ctx, offset, "chunk_offset"); + entries++; + } + track_offset = offset + sample.size; + + skip: + offset += sample.size; + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + } + vc_container_assert(entries == track_module->sample_table[MP4_SAMPLE_TABLE_STCO].entries); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_co64( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + WRITE_U32(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_CO64].entries, "entry_count"); + + if(module->null.refcount) + { + /* We're not actually writing the data, we just want the size */ + WRITE_BYTES(p_ctx, 0, track_module->sample_table[MP4_SAMPLE_TABLE_CO64].entries * 8); + return STREAM_STATUS(p_ctx); + } + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_stss( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_PACKET_T sample; + unsigned int entries = 0, samples = 0; + + memset(&sample, 0, sizeof(VC_CONTAINER_PACKET_T)); + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + WRITE_U32(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries, "entry_count"); + + if(module->null.refcount) + { + /* We're not actually writing the data, we just want the size */ + WRITE_BYTES(p_ctx, 0, track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries * 4); + return STREAM_STATUS(p_ctx); + } + + /* Go through all the samples written */ + vc_container_io_seek(module->temp.io, INT64_C(0)); + + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + while(status == VC_CONTAINER_SUCCESS) + { + if(sample.track != module->current_track) goto skip; + + samples++; + if(sample.flags & VC_CONTAINER_PACKET_FLAG_KEYFRAME) + { + WRITE_U32(p_ctx, samples, "sample_number"); + entries++; + } + + skip: + status = mp4_writer_read_sample_from_temp(p_ctx, &sample); + } + vc_container_assert(entries == track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_vide_avcC( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + + WRITE_U32(p_ctx, track->format->extradata_size + 8, "size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('a','v','c','C'), "type"); + WRITE_BYTES(p_ctx, track->format->extradata, track->format->extradata_size); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_vide_d263( VC_CONTAINER_T *p_ctx ) +{ + WRITE_U32(p_ctx, 8 + 7, "size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('d','2','6','3'), "type"); + WRITE_FOURCC(p_ctx, VC_FOURCC('B','R','C','M'), "vendor"); + WRITE_U8(p_ctx, 0, "version"); + WRITE_U8(p_ctx, 10, "level"); + WRITE_U8(p_ctx, 0, "profile"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_vide( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + unsigned int i; + + for(i = 0; i < 6; i++) WRITE_U8(p_ctx, 0, "reserved"); + WRITE_U16(p_ctx, 1, "data_reference_index"); + + WRITE_U16(p_ctx, 0, "pre_defined"); + WRITE_U16(p_ctx, 0, "reserved"); + for(i = 0; i < 3; i++) WRITE_U32(p_ctx, 0, "pre_defined"); + WRITE_U16(p_ctx, track->format->type->video.width, "width"); + WRITE_U16(p_ctx, track->format->type->video.height, "height"); + WRITE_U32(p_ctx, 0x480000, "horizresolution"); /* 72 dpi */ + WRITE_U32(p_ctx, 0x480000, "vertresolution"); /* 72 dpi */ + WRITE_U32(p_ctx, 0, "reserved"); + WRITE_U16(p_ctx, 1, "frame_count"); + for(i = 0; i < 32; i++) _WRITE_U8(p_ctx, 0); + WRITE_U16(p_ctx, 0x18, "depth"); + WRITE_U16(p_ctx, -1, "pre_defined"); + + switch(track->format->codec) + { + case VC_CONTAINER_CODEC_H264: return mp4_write_box_vide_avcC(p_ctx); + case VC_CONTAINER_CODEC_H263: return mp4_write_box_vide_d263(p_ctx); + case VC_CONTAINER_CODEC_MP4V: return mp4_write_box(p_ctx, MP4_BOX_TYPE_ESDS); + default: break; + } + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_soun_damr( VC_CONTAINER_T *p_ctx ) +{ + WRITE_U32(p_ctx, 8 + 8, "size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('d','a','m','r'), "type"); + WRITE_FOURCC(p_ctx, VC_FOURCC('B','R','C','M'), "vendor"); + WRITE_U8(p_ctx, 0, "version"); + WRITE_U8(p_ctx, 0x80, "mode_set"); + WRITE_U8(p_ctx, 0, "mode_change_period"); + WRITE_U8(p_ctx, 1, "frame_per_second"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_soun_dawp( VC_CONTAINER_T *p_ctx ) +{ + WRITE_U32(p_ctx, 8 + 5, "size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('d','a','w','p'), "type"); + WRITE_FOURCC(p_ctx, VC_FOURCC('B','R','C','M'), "vendor"); + WRITE_U8(p_ctx, 0, "version"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_soun_devc( VC_CONTAINER_T *p_ctx ) +{ + WRITE_U32(p_ctx, 8 + 6, "size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('d','e','v','c'), "type"); + WRITE_FOURCC(p_ctx, VC_FOURCC('B','R','C','M'), "vendor"); + WRITE_U8(p_ctx, 0, "version"); + WRITE_U8(p_ctx, 1, "samples_per_frame"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_soun( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + unsigned int i, version = 0; + + for(i = 0; i < 6; i++) WRITE_U8(p_ctx, 0, "reserved"); + WRITE_U16(p_ctx, 1, "data_reference_index"); + + if(module->brand == MP4_BRAND_QT) + { + if(track->format->codec == VC_CONTAINER_CODEC_MP4A) version = 1; + WRITE_U16(p_ctx, version, "version"); + WRITE_U16(p_ctx, 0, "revision_level"); + WRITE_U32(p_ctx, 0, "vendor"); + } + else + { + for(i = 0; i < 2; i++) WRITE_U32(p_ctx, 0, "reserved"); + } + + WRITE_U16(p_ctx, track->format->type->audio.channels, "channelcount"); + WRITE_U16(p_ctx, 0, "samplesize"); + WRITE_U16(p_ctx, 0, "pre_defined"); + WRITE_U16(p_ctx, 0, "reserved"); + WRITE_U32(p_ctx, track->format->type->audio.sample_rate << 16, "samplerate"); + + if(module->brand == MP4_BRAND_QT && version == 1) /* FIXME */ + { + WRITE_U32(p_ctx, 1024, "samples_per_packet"); + WRITE_U32(p_ctx, 1536, "bytes_per_packet"); + WRITE_U32(p_ctx, 2, "bytes_per_frame"); + WRITE_U32(p_ctx, 2, "bytes_per_sample"); + } + + switch(track->format->codec) + { + case VC_CONTAINER_CODEC_AMRNB: + case VC_CONTAINER_CODEC_AMRWB: + return mp4_write_box_soun_damr(p_ctx); + case VC_CONTAINER_CODEC_AMRWBP: + return mp4_write_box_soun_dawp(p_ctx); + case VC_CONTAINER_CODEC_EVRC: + return mp4_write_box_soun_devc(p_ctx); + case VC_CONTAINER_CODEC_MP4A: + case VC_CONTAINER_CODEC_MPGA: + return mp4_write_box(p_ctx, MP4_BOX_TYPE_ESDS); + default: break; + } + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_write_box_esds( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track]; + unsigned int decoder_specific_size = 0, decoder_config_size, sl_size; + unsigned int stream_type, object_type; + +#define MP4_GET_DESCRIPTOR_SIZE(size) \ + ((size) < 0x0080) ? 2 + (size) : ((size) < 0x4000) ? 3 + (size) : 4 + (size) +#define MP4_WRITE_DESCRIPTOR_HEADER(type, size) \ + LOG_FORMAT(p_ctx, "descriptor %x, size %i", type, size); _WRITE_U8(p_ctx, type); \ + if((size) >= 0x4000) _WRITE_U8(p_ctx, (((size) >> 14) & 0x7F) | 0x80); \ + if((size) >= 0x80 ) _WRITE_U8(p_ctx, (((size) >> 7 ) & 0x7F) | 0x80); \ + _WRITE_U8(p_ctx, (size) & 0x7F) + + /* We only support small size descriptors */ + if(track->format->extradata_size > 0x200000 - 100) + return VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; + + switch(track->format->es_type) + { + case VC_CONTAINER_ES_TYPE_VIDEO: stream_type = 0x4; break; + case VC_CONTAINER_ES_TYPE_AUDIO: stream_type = 0x5; break; + case VC_CONTAINER_ES_TYPE_SUBPICTURE: stream_type = 0x20; break; + default: return VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; + } + switch(track->format->codec) + { + case VC_CONTAINER_CODEC_MP4V: object_type = 0x20; break; + case VC_CONTAINER_CODEC_MP1V: object_type = 0x6B; break; + case VC_CONTAINER_CODEC_MP2V: object_type = 0x60; break; + case VC_CONTAINER_CODEC_JPEG: object_type = 0x6C; break; + case VC_CONTAINER_CODEC_MP4A: object_type = 0x40; break; + case VC_CONTAINER_CODEC_MPGA: + object_type = track->format->type->audio.sample_rate < 32000 ? 0x69 : 0x6B; break; + default: return VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; + } + + decoder_specific_size = MP4_GET_DESCRIPTOR_SIZE(track->format->extradata_size); + decoder_config_size = MP4_GET_DESCRIPTOR_SIZE(13 + decoder_specific_size); + sl_size = MP4_GET_DESCRIPTOR_SIZE(1); + + WRITE_U8(p_ctx, 0, "version"); + WRITE_U24(p_ctx, 0, "flags"); + + /* Write the ES descriptor */ + MP4_WRITE_DESCRIPTOR_HEADER(0x3, 3 + decoder_config_size + sl_size); + WRITE_U16(p_ctx, module->current_track + 1, "es_id"); + WRITE_U8(p_ctx, 0x1f, "flags"); /* stream_priority = 0x1f */ + + /* Write the Decoder Config descriptor */ + MP4_WRITE_DESCRIPTOR_HEADER(0x4, 13 + decoder_specific_size); + WRITE_U8(p_ctx, object_type, "object_type_indication"); + WRITE_U8(p_ctx, (stream_type << 2) | 1, "stream_type"); + WRITE_U24(p_ctx, 8000, "buffer_size_db"); + WRITE_U32(p_ctx, track->format->bitrate, "max_bitrate"); + WRITE_U32(p_ctx, track->format->bitrate, "avg_bitrate"); + if(track->format->extradata_size) + { + MP4_WRITE_DESCRIPTOR_HEADER(0x5, track->format->extradata_size); + WRITE_BYTES(p_ctx, track->format->extradata, track->format->extradata_size); + } + + /* Write the SL descriptor */ + MP4_WRITE_DESCRIPTOR_HEADER(0x6, 1); + WRITE_U8(p_ctx, 0x2, "flags"); + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + int64_t mdat_size; + + mdat_size = STREAM_POSITION(p_ctx) - module->mdat_offset; + + /* Write the moov box */ + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_MOOV); + + /* Finalise the mdat box */ + SEEK(p_ctx, module->mdat_offset); + WRITE_U32(p_ctx, (uint32_t)mdat_size, "mdat size" ); + + for(; p_ctx->tracks_num > 0; p_ctx->tracks_num--) + vc_container_free_track(p_ctx, p_ctx->tracks[p_ctx->tracks_num-1]); + + vc_container_writer_extraio_delete(p_ctx, &module->temp); + vc_container_writer_extraio_delete(p_ctx, &module->null); + free(module); + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_add_track( VC_CONTAINER_T *p_ctx, VC_CONTAINER_ES_FORMAT_T *format ) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_TRACK_T *track; + uint32_t type = 0; + + if(!(format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED)) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + /* Check we support this format */ + switch(format->codec) + { + case VC_CONTAINER_CODEC_AMRNB: type = VC_FOURCC('s','a','m','r'); break; + case VC_CONTAINER_CODEC_AMRWB: type = VC_FOURCC('s','a','w','b'); break; + case VC_CONTAINER_CODEC_AMRWBP: type = VC_FOURCC('s','a','w','p'); break; + case VC_CONTAINER_CODEC_EVRC: type = VC_FOURCC('s','e','v','c'); break; + case VC_CONTAINER_CODEC_MP4A: type = VC_FOURCC('m','p','4','a'); break; + case VC_CONTAINER_CODEC_MPGA: type = VC_FOURCC('m','p','4','a'); break; + + case VC_CONTAINER_CODEC_MP4V: type = VC_FOURCC('m','p','4','v'); break; + case VC_CONTAINER_CODEC_JPEG: type = VC_FOURCC('m','p','4','v'); break; + case VC_CONTAINER_CODEC_H263: type = VC_FOURCC('s','2','6','3'); break; + case VC_CONTAINER_CODEC_H264: + if(format->codec_variant == VC_FOURCC('a','v','c','C')) type = VC_FOURCC('a','v','c','1'); break; + case VC_CONTAINER_CODEC_MJPEG: type = VC_FOURCC('j','p','e','g'); break; + case VC_CONTAINER_CODEC_MJPEGA: type = VC_FOURCC('m','j','p','a'); break; + case VC_CONTAINER_CODEC_MJPEGB: type = VC_FOURCC('m','j','p','b'); break; + case VC_CONTAINER_CODEC_MP1V: type = VC_FOURCC('m','p','e','g'); break; + case VC_CONTAINER_CODEC_MP2V: type = VC_FOURCC('m','p','e','g'); break; + + default: type = 0; break; + } + + if(!type) return VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; + + /* Allocate and initialise track data */ + if(p_ctx->tracks_num >= MP4_TRACKS_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + p_ctx->tracks[p_ctx->tracks_num] = track = + vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); + if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + if(format->extradata_size) + { + status = vc_container_track_allocate_extradata( p_ctx, track, format->extradata_size ); + if(status) goto error; + } + + vc_container_format_copy(track->format, format, format->extradata_size); + track->priv->module->fourcc = type; + track->priv->module->offset = -1; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STTS].entry_size = 8; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSZ].entry_size = 4; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSC].entry_size = 12; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STCO].entry_size = 4; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSS].entry_size = 4; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_CO64].entry_size = 8; + track->priv->module->sample_table[MP4_SAMPLE_TABLE_CTTS].entry_size = 8; + + p_ctx->tracks_num++; + return VC_CONTAINER_SUCCESS; + + error: + vc_container_free_track(p_ctx, track); + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_add_track_done( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + if(module->tracks_add_done) return status; + + /* We need to find out the size of the object we're going to write it. */ + if(!vc_container_writer_extraio_enable(p_ctx, &module->null)) + { + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_MOOV); + module->moov_size = STREAM_POSITION(p_ctx); + p_ctx->size = module->moov_size; + } + vc_container_writer_extraio_disable(p_ctx, &module->null); + + if(status == VC_CONTAINER_SUCCESS) module->tracks_add_done = true; + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_control( VC_CONTAINER_T *p_ctx, VC_CONTAINER_CONTROL_T operation, va_list args ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + switch(operation) + { + case VC_CONTAINER_CONTROL_TRACK_ADD: + { + VC_CONTAINER_ES_FORMAT_T *p_format = + (VC_CONTAINER_ES_FORMAT_T *)va_arg( args, VC_CONTAINER_ES_FORMAT_T * ); + if(module->tracks_add_done) return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + return mp4_writer_add_track(p_ctx, p_format); + } + + case VC_CONTAINER_CONTROL_TRACK_ADD_DONE: + return mp4_writer_add_track_done(p_ctx); + + default: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + } +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_add_sample( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[packet->track]; + VC_CONTAINER_TRACK_MODULE_T *track_module = track->priv->module; + + track_module->last_pts = packet->pts; + if(!track_module->samples) track_module->first_pts = packet->pts; + + track_module->samples++; + track_module->sample_table[MP4_SAMPLE_TABLE_STSZ].entries++; /* sample size */ + p_ctx->size += track_module->sample_table[MP4_SAMPLE_TABLE_STSZ].entry_size; + track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries++; /* time to sample */ + p_ctx->size += track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entry_size; + + #if 0 + delta_ts = packet->dts - track_module->timestamp; + track_module->timestamp = packet->dts; + if(!track_module->samples) track_module->delta_ts = + if() +#endif + + /* Is it a new chunk ? */ + if(module->sample_offset != track_module->offset) + { + track_module->chunks++; + track_module->sample_table[MP4_SAMPLE_TABLE_STCO].entries++; /* chunk offset */ + p_ctx->size += track_module->sample_table[MP4_SAMPLE_TABLE_STCO].entry_size; + track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries++; /* sample to chunk */ + p_ctx->size += track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entry_size; + } + track_module->offset = module->sample_offset + packet->size; + + if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO && + (packet->flags & VC_CONTAINER_PACKET_FLAG_KEYFRAME)) + { + track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries++; /* sync sample */ + p_ctx->size += track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entry_size; + } + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mp4_writer_write( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_PACKET_T *sample = &module->sample; + VC_CONTAINER_STATUS_T status; + + if(!module->tracks_add_done) + { + status = mp4_writer_add_track_done(p_ctx); + if(status != VC_CONTAINER_SUCCESS) return status; + } + + if(packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_START) + ++module->samples; /* Switching to a new sample */ + + if(packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_START) + { + module->sample_offset = STREAM_POSITION(p_ctx); + sample->size = packet->size; + sample->pts = packet->pts; + sample->dts = packet->pts; + sample->track = packet->track; + sample->flags = packet->flags; + } + else + { + sample->size += packet->size; + sample->flags |= packet->flags; + } + + if(WRITE_BYTES(p_ctx, packet->data, packet->size) != packet->size) + return STREAM_STATUS(p_ctx); // TODO do something + p_ctx->size += packet->size; + + // + if(packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_END) + { + status = mp4_writer_write_sample_to_temp(p_ctx, sample); + status = mp4_writer_add_sample(p_ctx, sample); + } + + return VC_CONTAINER_SUCCESS; +} + +/****************************************************************************** +Global function definitions. +******************************************************************************/ + +VC_CONTAINER_STATUS_T mp4_writer_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + const char *extension = vc_uri_path_extension(p_ctx->priv->uri); + VC_CONTAINER_MODULE_T *module = 0; + MP4_BRAND_T brand; + + /* Check if the user has specified a container */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + /* Check we're the right writer for this */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(strcasecmp(extension, "3gp") && strcasecmp(extension, "skm") && + strcasecmp(extension, "mov") && strcasecmp(extension, "mp4") && + strcasecmp(extension, "m4v") && strcasecmp(extension, "m4a")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = module->tracks; + + /* Find out which brand we're going write */ + if(!strcasecmp(extension, "3gp")) brand = MP4_BRAND_3GP5; + else if(!strcasecmp(extension, "skm")) brand = MP4_BRAND_SKM2; + else if(!strcasecmp(extension, "mov")) brand = MP4_BRAND_QT; + else brand = MP4_BRAND_ISOM; + module->brand = brand; + + /* Create a null i/o writer to help us out in writing our data */ + status = vc_container_writer_extraio_create_null(p_ctx, &module->null); + if(status != VC_CONTAINER_SUCCESS) goto error; + + /* Create a temporary i/o writer to help us out in writing our data */ + status = vc_container_writer_extraio_create_temp(p_ctx, &module->temp); + if(status != VC_CONTAINER_SUCCESS) goto error; + + status = mp4_write_box(p_ctx, MP4_BOX_TYPE_FTYP); + if(status != VC_CONTAINER_SUCCESS) goto error; + + /* Start the mdat box */ + module->mdat_offset = STREAM_POSITION(p_ctx); + WRITE_U32(p_ctx, 0, "size"); + WRITE_FOURCC(p_ctx, VC_FOURCC('m','d','a','t'), "type"); + module->data_offset = STREAM_POSITION(p_ctx); + + p_ctx->priv->pf_close = mp4_writer_close; + p_ctx->priv->pf_write = mp4_writer_write; + p_ctx->priv->pf_control = mp4_writer_control; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "mp4: error opening stream"); + if(module) + { + if(module->null.io) vc_container_writer_extraio_delete(p_ctx, &module->null); + free(module); + } + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak writer_open mp4_writer_open +#endif diff --git a/containers/mpeg/CMakeLists.txt b/containers/mpeg/CMakeLists.txt new file mode 100644 index 0000000..5afe585 --- /dev/null +++ b/containers/mpeg/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_ps ${LIBRARY_TYPE} ps_reader.c) + +target_link_libraries(reader_ps containers) + +install(TARGETS reader_ps DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/mpeg/ps_reader.c b/containers/mpeg/ps_reader.c new file mode 100644 index 0000000..1394300 --- /dev/null +++ b/containers/mpeg/ps_reader.c @@ -0,0 +1,1268 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#define CONTAINER_IS_BIG_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#include "containers/core/containers_bits.h" +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" +#undef CONTAINER_HELPER_LOG_INDENT +#define CONTAINER_HELPER_LOG_INDENT(a) (2*(a)->priv->module->level) + +/****************************************************************************** +Defines. +******************************************************************************/ +#define PS_TRACKS_MAX 2 +#define PS_EXTRADATA_MAX 256 + +#define PS_SYNC_FAIL_MAX 65536 /** Maximum number of byte-wise sync attempts, + should be enough to stride at least one + PES packet (length encoded using 16 bits). */ + +/** Maximum number of pack/packet start codes scanned when searching for tracks + at open time or when resyncing. */ +#define PS_PACK_SCAN_MAX 128 + +/****************************************************************************** +Type definitions. +******************************************************************************/ +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + /** Coding and elementary stream id of the track */ + uint32_t stream_id; + + /** Sub-stream id (for private_stream_1 only) */ + uint32_t substream_id; + + /** PES packet payload offset (for private_stream_1) */ + unsigned int payload_offset; + + uint8_t extradata[PS_EXTRADATA_MAX]; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + /** Logging indentation level */ + uint32_t level; + + /** Track data */ + int tracks_num; + VC_CONTAINER_TRACK_T *tracks[PS_TRACKS_MAX]; + + /** State flag denoting whether or not we are searching + for tracks (at open time) */ + bool searching_tracks; + + /** Size of program stream data (if known) */ + uint64_t data_size; + + /** Offset to the first pack or PES packet start code we've seen */ + uint64_t data_offset; + + /** The first system_clock_reference value we've seen, in (27MHz ticks) */ + int64_t scr_offset; + + /** Most recent system_clock_reference value we've seen, in (27MHz ticks) */ + int64_t scr; + + /** Global offset we add to PES timestamps to make them zero based and + to work around discontinuity in the system_clock_reference */ + int64_t scr_bias; + + /** Most recent program stream mux rate (in units of 50 bytes/second). */ + uint32_t mux_rate; + + /** Offset to the most recent pack start code we've seen */ + uint64_t pack_offset; + + /** Program stream mux rate is often incorrect or fixed to 25200 (10.08 + Mbit/s) which yields inaccurate duration estimate for most files. We + maintain a moving average data rate (in units of bytes/second) based + on the system_clock_reference to give better estimates. */ + int64_t data_rate; + + /** Offset to the most recent PES packet start code prefix we've seen */ + unsigned int packet_data_size; + unsigned int packet_data_left; + int64_t packet_pts; + int64_t packet_dts; + int packet_track; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ + +VC_CONTAINER_STATUS_T ps_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Prototypes for local functions +******************************************************************************/ + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/** Find the track associated with a PS stream_id */ +static VC_CONTAINER_TRACK_T *ps_find_track( VC_CONTAINER_T *ctx, uint32_t stream_id, + uint32_t substream_id, bool b_create ) +{ + VC_CONTAINER_TRACK_T *track = 0; + unsigned int i; + + for(i = 0; i < ctx->tracks_num; i++) + if(ctx->tracks[i]->priv->module->stream_id == stream_id && + ctx->tracks[i]->priv->module->substream_id == substream_id) break; + + if(i < ctx->tracks_num) /* We found it */ + track = ctx->tracks[i]; + + if(!track && b_create && i < PS_TRACKS_MAX) + { + /* Allocate and initialise a new track */ + ctx->tracks[i] = track = + vc_container_allocate_track(ctx, sizeof(*ctx->tracks[0]->priv->module)); + if(track) + { + track->priv->module->stream_id = stream_id; + track->priv->module->substream_id = substream_id; + ctx->tracks_num++; + } + } + + if(!track && b_create) + LOG_DEBUG(ctx, "could not create track for stream id: %i", stream_id); + + return track; +} + +/*****************************************************************************/ +STATIC_INLINE VC_CONTAINER_STATUS_T ps_find_start_code( VC_CONTAINER_T *ctx, uint8_t *buffer ) +{ + unsigned int i; + + /* Scan for a pack or PES packet start code prefix */ + for (i = 0; i < PS_SYNC_FAIL_MAX; ++i) + { + if(PEEK_BYTES(ctx, buffer, 4) < 4) + return VC_CONTAINER_ERROR_EOS; + + if(buffer[0] == 0x0 && buffer[1] == 0x0 && buffer[2] == 0x1 && buffer[3] >= 0xB9) + break; + + if (SKIP_BYTES(ctx, 1) != 1) + return VC_CONTAINER_ERROR_EOS; + } + + if(i == PS_SYNC_FAIL_MAX) /* We didn't find a valid pack or PES packet */ + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if (buffer[3] == 0xB9) /* MPEG_program_end_code */ + return VC_CONTAINER_ERROR_EOS; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_system_header( VC_CONTAINER_T *ctx ) +{ + uint8_t header[8]; + uint32_t length; + VC_CONTAINER_BITS_T bits; + + if(_READ_U32(ctx) != 0x1BB) return VC_CONTAINER_ERROR_CORRUPTED; + LOG_FORMAT(ctx, "system_header"); + ctx->priv->module->level++; + + length = READ_U16(ctx, "header_length"); + if(length < 6) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 6) != 6) return VC_CONTAINER_ERROR_EOS; + + BITS_INIT(ctx, &bits, header, 6); + + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 22, "rate_bound"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 6, "audio_bound"); + BITS_SKIP(ctx, &bits, 1, "fixed_flag"); + BITS_SKIP(ctx, &bits, 1, "CSPS_flag"); + BITS_SKIP(ctx, &bits, 1, "system_audio_lock_flag"); + BITS_SKIP(ctx, &bits, 1, "system_video_lock_flag"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 5, "video_bound"); + BITS_SKIP(ctx, &bits, 1, "packet_rate_restriction_flag"); + BITS_SKIP(ctx, &bits, 7, "reserved_bits"); + length -= 6; + + while(length >= 3 && (PEEK_U8(ctx) & 0x80)) + { + SKIP_U8(ctx, "stream_id"); + SKIP_BYTES(ctx, 2); + length -= 3; + } + SKIP_BYTES(ctx, length); + + ctx->priv->module->level--; + return STREAM_STATUS(ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_pack_header( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + uint8_t header[10]; + int64_t scr, scr_base, scr_ext = INT64_C(0); + uint64_t pack_offset = STREAM_POSITION(ctx); + uint32_t mux_rate, stuffing; + VC_CONTAINER_BITS_T bits; + VC_CONTAINER_STATUS_T status; + + if(_READ_U32(ctx) != 0x1BA) return VC_CONTAINER_ERROR_CORRUPTED; + LOG_FORMAT(ctx, "pack_header"); + + module->level++; + + if (PEEK_U8(ctx) & 0x40) /* program stream */ + { + if(READ_BYTES(ctx, header, 10) != 10) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 10); + if(BITS_READ_U32(ctx, &bits, 2, "'01' marker bits") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + scr_base = BITS_READ_U32(ctx, &bits, 3, "system_clock_reference_base [32..30]") << 30; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + scr_base |= BITS_READ_U32(ctx, &bits, 15, "system_clock_reference_base [29..15]") << 15; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + scr_base |= BITS_READ_U32(ctx, &bits, 15, "system_clock_reference_base [14..0]"); + LOG_FORMAT(ctx, "system_clock_reference_base %"PRId64, scr_base); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + scr_ext = BITS_READ_U32(ctx, &bits, 9, "system_clock_reference_extension"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + mux_rate = BITS_READ_U32(ctx, &bits, 22, "program_mux_rate"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 5, "reserved"); + stuffing = BITS_READ_U32(ctx, &bits, 3, "pack_stuffing_length"); + SKIP_BYTES(ctx, stuffing); + } + else /* system stream */ + { + if(READ_BYTES(ctx, header, 8) != 8) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 8); + if(BITS_READ_U32(ctx, &bits, 4, "'0010' marker bits") != 0x2) return VC_CONTAINER_ERROR_CORRUPTED; + scr_base = BITS_READ_U32(ctx, &bits, 3, "system_clock_reference_base [32..30]") << 30; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + scr_base |= BITS_READ_U32(ctx, &bits, 15, "system_clock_reference_base [29..15]") << 15; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + scr_base |= BITS_READ_U32(ctx, &bits, 15, "system_clock_reference_base [14..0]"); + LOG_FORMAT(ctx, "system_clock_reference_base %"PRId64, scr_base); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + mux_rate = BITS_READ_U32(ctx, &bits, 22, "program_mux_rate"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + } + + if ((status = STREAM_STATUS(ctx)) != VC_CONTAINER_SUCCESS) return status; + + module->level--; + + /* Set or update system_clock_reference, adjust bias if necessary */ + scr = scr_base * INT64_C(300) + scr_ext; + + if (module->scr_offset == VC_CONTAINER_TIME_UNKNOWN) + module->scr_offset = scr; + + if (module->scr == VC_CONTAINER_TIME_UNKNOWN) + module->scr_bias = -scr; + else if (scr < module->scr) + module->scr_bias = module->scr - scr; + + if (module->scr != VC_CONTAINER_TIME_UNKNOWN) + { + /* system_clock_reference is not necessarily continuous across the entire stream */ + if (scr > module->scr) + { + int64_t data_rate; + data_rate = INT64_C(27000000) * (pack_offset - module->pack_offset) / (scr - module->scr); + + if (module->data_rate) + { + /* Simple moving average over data rate seen so far */ + module->data_rate = (module->data_rate * 31 + data_rate) >> 5; + } + else + { + module->data_rate = mux_rate * 50; + } + } + + module->pack_offset = pack_offset; + } + + module->scr = scr; + module->mux_rate = mux_rate; + + /* Check for a system header */ + if(PEEK_U32(ctx) == 0x1BB) + return ps_read_system_header(ctx); + + return STREAM_STATUS(ctx); +} + +/*****************************************************************************/ +static void ps_get_stream_coding( VC_CONTAINER_T *ctx, unsigned int stream_id, + VC_CONTAINER_ES_TYPE_T *p_type, VC_CONTAINER_FOURCC_T *p_codec, + VC_CONTAINER_FOURCC_T *p_variant) +{ + VC_CONTAINER_ES_TYPE_T type = VC_CONTAINER_ES_TYPE_UNKNOWN; + VC_CONTAINER_FOURCC_T codec = VC_CONTAINER_CODEC_UNKNOWN; + VC_CONTAINER_FOURCC_T variant = 0; + + VC_CONTAINER_PARAM_UNUSED(ctx); + + if (stream_id == 0xE2) /* FIXME: why is this stream number reserved for H264? */ + { + type = VC_CONTAINER_ES_TYPE_VIDEO; + codec = VC_CONTAINER_CODEC_H264; + } + else if ((stream_id & 0xF0) == 0xE0) + { + type = VC_CONTAINER_ES_TYPE_VIDEO; + codec = VC_CONTAINER_CODEC_MP2V; + } + else if ((stream_id & 0xE0) == 0xC0) + { + type = VC_CONTAINER_ES_TYPE_AUDIO; + codec = VC_CONTAINER_CODEC_MPGA; + variant = VC_CONTAINER_VARIANT_MPGA_L2; + } + + /* FIXME: PRIVATE_EVOB_PES_PACKET with stream_id 0xFD ? */ + + *p_type = type; + *p_codec = codec; + *p_variant = variant; +} + +/*****************************************************************************/ +static int64_t ps_pes_time_to_us( VC_CONTAINER_T *ctx, int64_t time ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + + if (time == VC_CONTAINER_TIME_UNKNOWN) + return VC_CONTAINER_TIME_UNKNOWN; + + /* Need to wait for system_clock_reference first */ + if (module->scr_bias == VC_CONTAINER_TIME_UNKNOWN) + return VC_CONTAINER_TIME_UNKNOWN; + + /* Can't have valid bias without known system_clock_reference */ + vc_container_assert(module->scr != VC_CONTAINER_TIME_UNKNOWN); + + /* 90kHz (PES) clock --> (zero based) 27MHz system clock --> microseconds */ + return (INT64_C(300) * time + module->scr_bias) / INT64_C(27); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_pes_time( VC_CONTAINER_T *ctx, + uint32_t *p_length, unsigned int pts_dts, int64_t *p_pts, int64_t *p_dts ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint8_t header[10]; + uint32_t length = *p_length; + VC_CONTAINER_BITS_T bits; + int64_t pts, dts; + + if (p_pts) *p_pts = VC_CONTAINER_TIME_UNKNOWN; + if (p_dts) *p_dts = VC_CONTAINER_TIME_UNKNOWN; + + if (pts_dts == 0x2) + { + /* PTS only */ + LOG_FORMAT(ctx, "PTS"); + ctx->priv->module->level++; + if(length < 5) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 5) != 5) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 5); + + if(BITS_READ_U32(ctx, &bits, 4, "'0010' marker bits") != 0x2) return VC_CONTAINER_ERROR_CORRUPTED; + pts = BITS_READ_U32(ctx, &bits, 3, "PTS [32..30]") << 30; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + pts |= BITS_READ_U32(ctx, &bits, 15, "PTS [29..15]") << 15; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + pts |= BITS_READ_U32(ctx, &bits, 15, "PTS [14..0]"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + LOG_FORMAT(ctx, "PTS %"PRId64, pts); + if (p_pts) *p_pts = pts; + length -= 5; + ctx->priv->module->level--; + } + else if (pts_dts == 0x3) + { + /* PTS & DTS */ + LOG_FORMAT(ctx, "PTS DTS"); + ctx->priv->module->level++; + if(length < 10) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 10) != 10) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 10); + + /* PTS */ + if(BITS_READ_U32(ctx, &bits, 4, "'0011' marker bits") != 0x3) return VC_CONTAINER_ERROR_CORRUPTED; + pts = BITS_READ_U32(ctx, &bits, 3, "PTS [32..30]") << 30; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + pts |= BITS_READ_U32(ctx, &bits, 15, "PTS [29..15]") << 15; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + pts |= BITS_READ_U32(ctx, &bits, 15, "PTS [14..0]"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + + /* DTS */ + if(BITS_READ_U32(ctx, &bits, 4, "'0001' marker bits") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + dts = BITS_READ_U32(ctx, &bits, 3, "DTS [32..30]") << 30; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + dts |= BITS_READ_U32(ctx, &bits, 15, "DTS [29..15]") << 15; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + dts |= BITS_READ_U32(ctx, &bits, 15, "DTS [14..0]"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + LOG_FORMAT(ctx, "PTS %"PRId64, pts); + LOG_FORMAT(ctx, "DTS %"PRId64, dts); + if (p_pts) *p_pts = pts; + if (p_dts) *p_dts = dts; + length -= 10; + ctx->priv->module->level--; + } + else + { + status = VC_CONTAINER_ERROR_NOT_FOUND; + } + + *p_length = *p_length - length; + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_pes_extension( VC_CONTAINER_T *ctx, + uint32_t *p_length ) +{ + unsigned int pes_private_data, pack_header, packet_seq_counter, pstd_buffer, extension2; + uint8_t header[2]; + uint32_t length = *p_length; + VC_CONTAINER_BITS_T bits; + unsigned int i; + + LOG_FORMAT(ctx, "PES_extension"); + ctx->priv->module->level++; + if(length < 1) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 1) != 1) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 1); + + pes_private_data = BITS_READ_U32(ctx, &bits, 1, "PES_private_data_flag"); + pack_header = BITS_READ_U32(ctx, &bits, 1, "pack_header_field_flag"); + packet_seq_counter = BITS_READ_U32(ctx, &bits, 1, "program_packet_sequence_counter_flag"); + pstd_buffer = BITS_READ_U32(ctx, &bits, 1, "P-STD_buffer_flag"); + BITS_SKIP(ctx, &bits, 3, "3 reserved_bits"); + extension2 = BITS_READ_U32(ctx, &bits, 1, "PES_extension_flag_2"); + length -= 1; + + if (pes_private_data) + { + if(length < 16) return VC_CONTAINER_ERROR_CORRUPTED; + SKIP_BYTES(ctx, 16); /* PES_private_data */ + length -= 16; + } + + if (pack_header) + { + unsigned int pack_field_len; + if(length < 1) return VC_CONTAINER_ERROR_CORRUPTED; + pack_field_len = READ_U8(ctx, "pack_field_length"); + length -= 1; + if(length < pack_field_len) return VC_CONTAINER_ERROR_CORRUPTED; + SKIP_BYTES(ctx, pack_field_len); /* pack_header */ + length -= pack_field_len; + } + + if (packet_seq_counter) + { + if(length < 2) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 2) != 2) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 2); + + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 7, "program_packet_sequence_counter"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 1, "MPEG1_MPEG2_identifier"); + BITS_SKIP(ctx, &bits, 6, "original_stuff_length"); + length -= 2; + } + + if (pstd_buffer) + { + if(length < 2) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 2) != 2) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 2); + + if(BITS_READ_U32(ctx, &bits, 2, "'01' marker bits") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 1, "P-STD_buffer_scale"); + BITS_SKIP(ctx, &bits, 13, "P-STD_buffer_size"); + length -= 2; + } + + if (extension2) + { + uint8_t ext_field_len; + + if(length < 1) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, &ext_field_len, 1) != 1) return VC_CONTAINER_ERROR_EOS; + length -= 1; + + if((ext_field_len & 0x80) != 0x80) return VC_CONTAINER_ERROR_CORRUPTED; /* marker_bit */ + ext_field_len &= ~0x80; + LOG_FORMAT(ctx, "PES_extension_field_length %d", ext_field_len); + + for (i = 0; i < ext_field_len; i++) + { + SKIP_U8(ctx, "reserved"); + length--; + } + } + + ctx->priv->module->level--; + + *p_length = *p_length - length; /* Number of bytes read from stream */ + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_pes_packet_header( VC_CONTAINER_T *ctx, + uint32_t *p_length, int64_t *p_pts, int64_t *p_dts ) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_BITS_T bits; + uint32_t size, length = *p_length; + unsigned int pts_dts; + uint8_t header[10]; + + if(length < 3) return VC_CONTAINER_ERROR_CORRUPTED; + + if ((PEEK_U8(ctx) & 0xC0) == 0x80) /* program stream */ + { + unsigned int escr, es_rate, dsm_trick_mode, additional_copy_info, pes_crc, pes_extension; + unsigned int header_length; + + if(READ_BYTES(ctx, header, 3) != 3) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 3); + + if (BITS_READ_U32(ctx, &bits, 2, "'10' marker bits") != 0x2) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 2, "PES_scrambling_control"); + BITS_SKIP(ctx, &bits, 1, "PES_priority"); + BITS_SKIP(ctx, &bits, 1, "data_alignment_indicator"); + BITS_SKIP(ctx, &bits, 1, "copyright"); + BITS_SKIP(ctx, &bits, 1, "original_or_copy"); + pts_dts = BITS_READ_U32(ctx, &bits, 2, "PTS_DTS_flags"); + escr = BITS_READ_U32(ctx, &bits, 1, "ESCR_flag"); + es_rate = BITS_READ_U32(ctx, &bits, 1, "ES_rate_flag"); + dsm_trick_mode = BITS_READ_U32(ctx, &bits, 1, "DSM_trick_mode_flag"); + additional_copy_info = BITS_READ_U32(ctx, &bits, 1, "additional_copy_info_flag"); + pes_crc = BITS_READ_U32(ctx, &bits, 1, "PES_CRC_flag"); + pes_extension = BITS_READ_U32(ctx, &bits, 1, "PES_extension_flag"); + header_length = BITS_READ_U32(ctx, &bits, 8, "PES_header_data_length"); + length -= 3; + + size = length; + status = ps_read_pes_time(ctx, &size, pts_dts, p_pts, p_dts); + if (status && status != VC_CONTAINER_ERROR_NOT_FOUND) return status; + length -= size; + header_length -= size; + + if (escr) + { + /* Elementary stream clock reference */ + int64_t escr; + + ctx->priv->module->level++; + if(length < 6) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 6) != 6) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 6); + + BITS_SKIP(ctx, &bits, 2, "reserved_bits"); + escr = BITS_READ_U32(ctx, &bits, 3, "ESCR_base [32..30]") << 30; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + escr |= BITS_READ_U32(ctx, &bits, 15, "ESCR_base [29..15]") << 15; + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + escr |= BITS_READ_U32(ctx, &bits, 15, "ESCR_base [14..0]"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_READ_U32(ctx, &bits, 9, "ESCR_extension"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + + LOG_FORMAT(ctx, "ESCR_base %"PRId64, escr); + length -= 6; + header_length -= 6; + ctx->priv->module->level--; + } + + if (es_rate) + { + /* Elementary stream rate */ + if(length < 3) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 3) != 3) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 3); + + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_READ_U32(ctx, &bits, 22, "ES_rate"); + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + length -= 3; + header_length -= 3; + } + + if (dsm_trick_mode) + { + unsigned int trick_mode; + + if(length < 1) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 1) != 1) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 1); + + trick_mode = BITS_READ_U32(ctx, &bits, 3, "trick_mode_control"); + if (trick_mode == 0x0 /* fast_forward */) + { + BITS_SKIP(ctx, &bits, 2, "field_id"); + BITS_SKIP(ctx, &bits, 1, "intra_slice_refresh"); + BITS_SKIP(ctx, &bits, 2, "frequency_truncation"); + } + else if (trick_mode == 0x1 /* slow_motion */) + { + BITS_SKIP(ctx, &bits, 5, "rep_cntrl"); + } + else if (trick_mode == 0x2 /* freeze_frame */) + { + BITS_SKIP(ctx, &bits, 2, "field_id"); + BITS_SKIP(ctx, &bits, 3, "reserved_bits"); + } + else if (trick_mode == 0x3 /* fast_reverse */) + { + BITS_SKIP(ctx, &bits, 2, "field_id"); + BITS_SKIP(ctx, &bits, 1, "intra_slice_refresh"); + BITS_SKIP(ctx, &bits, 2, "frequency_truncation"); + } + else if (trick_mode == 0x4 /* slow_reverse */) + BITS_SKIP(ctx, &bits, 5, "rep_cntrl"); + else + BITS_SKIP(ctx, &bits, 5, "5 reserved_bits"); + + length -= 1; + header_length -= 1; + } + + if (additional_copy_info) + { + if(length < 1) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 1) != 1) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 1); + + if(BITS_READ_U32(ctx, &bits, 1, "marker_bit") != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + BITS_SKIP(ctx, &bits, 7, "additional_copy_info"); + + length -= 1; + header_length -= 1; + } + + if (pes_crc) + { + SKIP_U16(ctx, "previous_PES_packet_CRC"); + length -= 2; + header_length -= 2; + } + + if (pes_extension) + { + size = length; + if ((status = ps_read_pes_extension(ctx, &size)) != VC_CONTAINER_SUCCESS) return status; + length -= size; + header_length -= size; + } + + if (header_length <= length) + { + SKIP_BYTES(ctx, header_length); /* header stuffing */ + length -= header_length; + } + } + else /* MPEG 1 PES header */ + { + if(length < 12) return VC_CONTAINER_ERROR_CORRUPTED; + + while (PEEK_U8(ctx) == 0xFF && length > 0) + { + SKIP_U8(ctx, "stuffing"); + length--; + } + + if (length == 0) return VC_CONTAINER_ERROR_CORRUPTED; + + if ((PEEK_U8(ctx) & 0xC0) == 0x40) + { + if(length < 2) return VC_CONTAINER_ERROR_CORRUPTED; + SKIP_U8(ctx, "???"); + SKIP_U8(ctx, "???"); + length -= 2; + } + + pts_dts = (PEEK_U8(ctx) & 0x30) >> 4; + size = length; + status = ps_read_pes_time(ctx, &size, pts_dts, p_pts, p_dts); + if (status && status != VC_CONTAINER_ERROR_NOT_FOUND) + return status; + length -= size; + + if (status == VC_CONTAINER_ERROR_NOT_FOUND) + { + if(length < 1) return VC_CONTAINER_ERROR_CORRUPTED; + SKIP_U8(ctx, "???"); + length -= 1; + } + } + + *p_length = length; + return STREAM_STATUS(ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_private_stream_1_coding( VC_CONTAINER_T *ctx, + VC_CONTAINER_ES_TYPE_T *p_type, VC_CONTAINER_FOURCC_T *p_codec, + uint32_t *substream_id, uint32_t *p_length ) +{ + VC_CONTAINER_ES_TYPE_T type = VC_CONTAINER_ES_TYPE_UNKNOWN; + VC_CONTAINER_FOURCC_T codec = VC_CONTAINER_CODEC_UNKNOWN; + uint32_t length; + uint8_t id = 0; + + length = *p_length; + + if(length < 1) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, &id, 1) != 1) return VC_CONTAINER_ERROR_EOS; + length -= 1; + + LOG_FORMAT(ctx, "private_stream_1 byte: 0x%x (%u)", id, id); + + if (id >= 0x20 && id <= 0x3f) + { + type = VC_CONTAINER_ES_TYPE_SUBPICTURE; + codec = VC_CONTAINER_CODEC_UNKNOWN; + } + else if ((id >= 0x80 && id <= 0x87) || (id >= 0xC0 && id <= 0xCF)) + { + type = VC_CONTAINER_ES_TYPE_AUDIO; + codec = VC_CONTAINER_CODEC_AC3; + } + else if ((id >= 0x88 && id <= 0x8F) || (id >= 0x98 && id <= 0x9F)) + { + type = VC_CONTAINER_ES_TYPE_AUDIO; + codec = VC_CONTAINER_CODEC_DTS; + } + else if (id >= 0xA0 && id <= 0xBF) + { + type = VC_CONTAINER_ES_TYPE_AUDIO; + codec = VC_CONTAINER_CODEC_PCM_SIGNED; + } + else + { + LOG_FORMAT(ctx, "Unknown private_stream_1 byte: 0x%x (%u)", id, id); + } + + *substream_id = id; + *p_type = type; + *p_codec = codec; + *p_length = length; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_private_stream_1_format( VC_CONTAINER_T *ctx, + VC_CONTAINER_ES_FORMAT_T *format, uint32_t *length ) +{ + uint8_t header[8]; + VC_CONTAINER_BITS_T bits; + + if (format->codec == VC_CONTAINER_CODEC_PCM_SIGNED) + { + static const unsigned fs_tab[4] = { 48000, 96000, 44100, 32000 }; + static const unsigned bps_tab[] = {16, 20, 24, 0}; + + unsigned fs, bps, nchan; + + if(*length < 6) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 6) != 6) return VC_CONTAINER_ERROR_EOS; + BITS_INIT(ctx, &bits, header, 6); + + BITS_SKIP(ctx, &bits, 8, "???"); + BITS_SKIP(ctx, &bits, 8, "???"); + BITS_SKIP(ctx, &bits, 8, "???"); + BITS_SKIP(ctx, &bits, 1, "emphasis"); + BITS_SKIP(ctx, &bits, 1, "mute"); + BITS_SKIP(ctx, &bits, 1, "reserved"); + BITS_SKIP(ctx, &bits, 5, "frame number"); + bps = BITS_READ_U32(ctx, &bits, 2, "quant"); + fs = BITS_READ_U32(ctx, &bits, 2, "freq"); + BITS_SKIP(ctx, &bits, 1, "reserved"); + nchan = BITS_READ_U32(ctx, &bits, 3, "channels"); + *length -= 6; + + format->type->audio.sample_rate = fs_tab[fs]; + format->type->audio.bits_per_sample = bps_tab[bps]; + format->type->audio.channels = nchan + 1; + format->type->audio.block_align = + (format->type->audio.channels * format->type->audio.bits_per_sample + 7 ) / 8; + } + else + { + if(*length < 3) return VC_CONTAINER_ERROR_CORRUPTED; + SKIP_U8(ctx, "num of frames"); + SKIP_U8(ctx, "start pos hi"); + SKIP_U8(ctx, "start pos lo"); + *length -= 3; + } + + return STREAM_STATUS(ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_read_pes_packet( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint8_t header[10]; + VC_CONTAINER_BITS_T bits; + uint32_t length, stream_id, substream_id = 0; + VC_CONTAINER_ES_TYPE_T type; + VC_CONTAINER_FOURCC_T codec, variant = 0; + VC_CONTAINER_TRACK_T *track; + int64_t pts, dts; + + if(_READ_U24(ctx) != 0x1) return VC_CONTAINER_ERROR_CORRUPTED; + if(READ_BYTES(ctx, header, 3) != 3) return VC_CONTAINER_ERROR_EOS; + LOG_FORMAT(ctx, "pes_packet_header"); + module->level++; + + BITS_INIT(ctx, &bits, header, 3); + stream_id = BITS_READ_U32(ctx, &bits, 8, "stream_id"); + length = BITS_READ_U32(ctx, &bits, 16, "PES_packet_length"); + + if (stream_id < 0xBC) return VC_CONTAINER_ERROR_CORRUPTED; + + if (stream_id == 0xBC /* program_stream_map */ || stream_id == 0xBE /* padding_stream */ || + stream_id == 0xBF /* private_stream_2 */ || stream_id == 0xF0 /* ECM */ || + stream_id == 0xF1 /* EMM */ || stream_id == 0xFF /* program_stream_directory */ || + stream_id == 0xF2 /* DSMCC_stream */ || stream_id == 0xF8 /* ITU-T Rec. H.222.1 type E */) + goto skip; + + /* Parse PES packet header */ + if ((status = ps_read_pes_packet_header(ctx, &length, &pts, &dts)) != VC_CONTAINER_SUCCESS) + return status; + + /* For private_stream_1, encoding format is stored in the payload */ + if (stream_id == 0xBD) + { + status = ps_read_private_stream_1_coding(ctx, &type, &codec, &substream_id, &length); + if (status) return status; + } + else + ps_get_stream_coding(ctx, stream_id, &type, &codec, &variant); + + /* Check that we know what to do with this track */ + if(type == VC_CONTAINER_ES_TYPE_UNKNOWN || codec == VC_CONTAINER_CODEC_UNKNOWN) + goto skip; + + track = ps_find_track(ctx, stream_id, substream_id, module->searching_tracks); + if(!track) goto skip; + + if (module->searching_tracks) + { + track->is_enabled = true; + track->format->es_type = type; + track->format->codec = codec; + track->format->codec_variant = variant; + + /* For private_stream_1, we need to parse payload further to get elementary stream + format */ + if (stream_id == 0xBD) + { + uint32_t current_length = length; + status = ps_read_private_stream_1_format(ctx, track->format, &length); + if (status) return status; + track->priv->module->payload_offset = current_length - length; + } + + goto skip; + } + else + { + unsigned i; + SKIP_BYTES(ctx, track->priv->module->payload_offset); + length -= track->priv->module->payload_offset; + + /* Find track index */ + for(i = 0; i < ctx->tracks_num; i++) + if(ctx->tracks[i] == track) break; + vc_container_assert(i < ctx->tracks_num); + + module->packet_track = i; + module->packet_data_size = length; + module->packet_pts = pts; + module->packet_dts = dts; + } + +end: + module->level--; + return STREAM_STATUS(ctx); + +skip: + SKIP_BYTES(ctx, length); /* remaining PES_packet_data */ + goto end; +} + +/*****************************************************************************/ +STATIC_INLINE VC_CONTAINER_STATUS_T ps_find_pes_packet( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + uint8_t buffer[4]; + unsigned int i; + + module->packet_data_size = 0; + + for (i = 0; i != PS_PACK_SCAN_MAX; ++i) + { + if((status = ps_find_start_code(ctx, buffer)) != VC_CONTAINER_SUCCESS) + break; + + if (buffer[3] == 0xBA && ((status = ps_read_pack_header(ctx)) != VC_CONTAINER_SUCCESS)) + continue; /* pack start code but parsing failed, goto resync */ + + if ((status = ps_read_pes_packet(ctx)) == VC_CONTAINER_SUCCESS) + break; + } + + return status; +} + +/***************************************************************************** +Functions exported as part of the Container Module API +*****************************************************************************/ + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_reader_read( VC_CONTAINER_T *ctx, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + + vc_container_assert(!module->searching_tracks); + + while(!module->packet_data_left) + { + if(ps_find_pes_packet(ctx) != VC_CONTAINER_SUCCESS) + { + status = VC_CONTAINER_ERROR_EOS; + goto error; + } + + module->packet_data_left = module->packet_data_size; + } + + p_packet->track = module->packet_track; + p_packet->size = module->packet_data_left; + p_packet->flags = 0; + p_packet->pts = ps_pes_time_to_us(ctx, module->packet_pts); + p_packet->dts = ps_pes_time_to_us(ctx, module->packet_dts); + + if (flags & VC_CONTAINER_READ_FLAG_SKIP) + { + SKIP_BYTES(ctx, module->packet_data_left); + module->packet_data_left = 0; + return VC_CONTAINER_SUCCESS; + } + + if (flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + p_packet->size = MIN(p_packet->buffer_size, module->packet_data_left); + p_packet->size = READ_BYTES(ctx, p_packet->data, p_packet->size); + module->packet_data_left -= p_packet->size; + + /* Temporary work-around for lpcm audio */ + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[module->packet_track]; + if (track->format->codec == VC_CONTAINER_CODEC_PCM_SIGNED) + { + unsigned i; + uint16_t *ptr = (uint16_t *)p_packet->data; + + for (i = 0; i < p_packet->size / 2; i ++) + { + uint32_t v = *ptr; + *ptr++ = v >> 8 | ( (v & 0xFF) << 8 ); + } + } + } + + if (module->packet_data_left) + module->packet_pts = module->packet_dts = VC_CONTAINER_TIME_UNKNOWN; + + return STREAM_STATUS(ctx); + +error: + if (status == VC_CONTAINER_ERROR_EOS) + { + /* Reset time reference and calculation state */ + ctx->priv->module->scr = VC_CONTAINER_TIME_UNKNOWN; + ctx->priv->module->scr_bias = -module->scr_offset; + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_reader_seek( VC_CONTAINER_T *ctx, + int64_t *p_offset, VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint64_t seekpos, position; + int64_t scr; + + VC_CONTAINER_PARAM_UNUSED(flags); + + if (mode != VC_CONTAINER_SEEK_MODE_TIME || !STREAM_SEEKABLE(ctx)) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + position = STREAM_POSITION(ctx); + scr = module->scr; + + if (*p_offset == INT64_C(0)) + seekpos = module->data_offset; + else + { + if (!ctx->duration) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + /* The following is an estimate that might be quite inaccurate */ + seekpos = module->data_offset + (*p_offset * module->data_size) / ctx->duration; + } + + SEEK(ctx, seekpos); + module->scr = module->scr_offset; + status = ps_find_pes_packet(ctx); + if (status && status != VC_CONTAINER_ERROR_EOS) + goto error; + + module->packet_data_left = module->packet_data_size; + + if (module->packet_pts != VC_CONTAINER_TIME_UNKNOWN) + *p_offset = ps_pes_time_to_us(ctx, module->packet_pts); + else if (module->data_size) + *p_offset = (STREAM_POSITION(ctx) - module->data_offset) * ctx->duration / module->data_size; + + return STREAM_STATUS(ctx); + +error: + module->scr = scr; + SEEK(ctx, position); + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T ps_reader_close( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + unsigned int i; + + for(i = 0; i < ctx->tracks_num; i++) + vc_container_free_track(ctx, ctx->tracks[i]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T ps_reader_open( VC_CONTAINER_T *ctx ) +{ + const char *extension = vc_uri_path_extension(ctx->priv->uri); + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + uint8_t buffer[4]; + unsigned int i; + + /* Check if the user has specified a container */ + vc_uri_find_query(ctx->priv->uri, 0, "container", &extension); + + /* Since MPEG is difficult to auto-detect, we use the extension as + part of the autodetection */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(strcasecmp(extension, "ps") && strcasecmp(extension, "vob") && + strcasecmp(extension, "mpg") && strcasecmp(extension, "mp2") && + strcasecmp(extension, "mp3") && strcasecmp(extension, "mpeg")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if((status = ps_find_start_code(ctx, buffer)) != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; /* We didn't find a valid pack or PES packet */ + + LOG_DEBUG(ctx, "using ps reader"); + + /* We are probably dealing with a PS file */ + LOG_FORMAT(ctx, "MPEG PS reader, found start code: 0x%02x%02x%02x%02x", + buffer[0], buffer[1], buffer[2], buffer[3]); + + /* Need to allocate context before searching for streams */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + ctx->priv->module = module; + ctx->tracks = module->tracks; + + /* Store offset so we can get back to what we consider the first pack or + packet */ + module->data_offset = STREAM_POSITION(ctx); + + /* Search for tracks, reset time reference and calculation state first */ + ctx->priv->module->scr_offset = ctx->priv->module->scr = VC_CONTAINER_TIME_UNKNOWN; + ctx->priv->module->searching_tracks = true; + + for (i = 0; i != PS_PACK_SCAN_MAX; ++i) + { + if (buffer[3] == 0xBA && (ps_read_pack_header(ctx) != VC_CONTAINER_SUCCESS)) + goto resync; + + if (ps_read_pes_packet(ctx) == VC_CONTAINER_SUCCESS) + continue; + +resync: + LOG_DEBUG(ctx, "Lost sync, scanning for start code"); + if((status = ps_find_start_code(ctx, buffer)) != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_CORRUPTED; + LOG_DEBUG(ctx, "MPEG PS reader, found start code: 0x%"PRIx64" (%"PRId64"): 0x%02x%02x%02x%02x", + STREAM_POSITION(ctx), STREAM_POSITION(ctx), buffer[0], buffer[1], buffer[2], buffer[3]); + } + + /* Seek back to the start of data */ + SEEK(ctx, module->data_offset); + + /* Bail out if we didn't find any tracks */ + if(!ctx->tracks_num) + { + status = VC_CONTAINER_ERROR_NO_TRACK_AVAILABLE; + goto error; + } + + /* Set data size (necessary for seeking) */ + module->data_size = MAX(ctx->priv->io->size - module->data_offset, INT64_C(0)); + + /* Estimate data rate (necessary for seeking) */ + if(STREAM_SEEKABLE(ctx)) + { + /* Estimate data rate by jumping in the stream */ + #define PS_PACK_SEARCH_MAX 64 + uint64_t position = module->data_offset; + for (i = 0; i != PS_PACK_SEARCH_MAX; ++i) + { + position += (module->data_size / (PS_PACK_SEARCH_MAX + 1)); + SEEK(ctx, position); + + for(;;) + { + if(ps_find_start_code(ctx, buffer) != VC_CONTAINER_SUCCESS) + break; + + if (buffer[3] == 0xBA) + { + if (ps_read_pack_header(ctx) == VC_CONTAINER_SUCCESS) + break; + } + else + { + /* Skip PES packet */ + unsigned length; + SKIP_U32(ctx, "PES packet startcode"); + length = READ_U16(ctx, "PES packet length"); + SKIP_BYTES(ctx, length); + } + } + } + + ctx->duration = (INT64_C(1000000) * module->data_size) / (module->data_rate); + + if (module->scr > module->scr_offset) + { + int64_t delta = (module->scr - module->scr_offset) / INT64_C(27); + + if (delta > ctx->duration) + ctx->duration = delta; + } + + /* Seek back to the start of data */ + SEEK(ctx, module->data_offset); + } + else + { + /* For most files, program_mux_rate is not reliable at all */ + ctx->duration = (INT64_C(100000) * module->data_size) / (INT64_C(5) * module->mux_rate); + } + + /* Reset time reference and calculation state, we're now ready to read data */ + module->scr = VC_CONTAINER_TIME_UNKNOWN; + module->scr_bias = VC_CONTAINER_TIME_UNKNOWN; + + ctx->priv->module->searching_tracks = false; + + if(STREAM_SEEKABLE(ctx)) ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + + ctx->priv->pf_close = ps_reader_close; + ctx->priv->pf_read = ps_reader_read; + ctx->priv->pf_seek = ps_reader_seek; + + return STREAM_STATUS(ctx); + + error: + LOG_DEBUG(ctx, "ps: error opening stream (%i)", status); + if(module) ps_reader_close(ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open ps_reader_open +#endif diff --git a/containers/mpga/CMakeLists.txt b/containers/mpga/CMakeLists.txt new file mode 100644 index 0000000..79cf39a --- /dev/null +++ b/containers/mpga/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_mpga ${LIBRARY_TYPE} mpga_reader.c) + +target_link_libraries(reader_mpga containers) + +install(TARGETS reader_mpga DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/mpga/mpga_common.h b/containers/mpga/mpga_common.h new file mode 100644 index 0000000..b8ba8e5 --- /dev/null +++ b/containers/mpga/mpga_common.h @@ -0,0 +1,147 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#define MPGA_HEADER_SIZE 6 + +#define MPGA_MODE_STEREO 0 +#define MPGA_MODE_JSTEREO 1 +#define MPGA_MODE_DUAL 2 +#define MPGA_MODE_MONO 3 + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_read_header( uint8_t frame_header[MPGA_HEADER_SIZE], + uint32_t *p_frame_size, unsigned int *p_frame_bitrate, unsigned int *p_version, + unsigned int *p_layer, unsigned int *p_sample_rate, unsigned int *p_channels, + unsigned int *p_frame_size_samples, unsigned int *p_offset ) +{ + static const uint16_t mpga_bitrate[2][3][15] = + {{{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}, /* MPEG1, Layer 1 */ + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, /* MPEG1, Layer 2 */ + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}},/* MPEG1, Layer 3 */ + {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, /* MPEG2 and MPEG2.5, Layer 1 */ + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, /* MPEG2 and MPEG2.5, Layer 2 */ + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}} /* MPEG2 and MPEG2.5, Layer 3 */}; + static const uint16_t mpga_sample_rate[] = {44100, 48000, 32000}; + static const uint16_t mpga_frame_size[] = {384, 1152, 576}; + + unsigned int version, layer, br_id, sr_id, emphasis; + unsigned int bitrate, sample_rate, padding, mode; + + /* Check frame sync, 11 bits as we want to allow for MPEG2.5 */ + if (frame_header[0] != 0xff || (frame_header[1] & 0xe0) != 0xe0) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + version = 4 - ((frame_header[1] >> 3) & 3); + layer = 4 - ((frame_header[1] >> 1) & 3 ); + br_id = (frame_header[2] >> 4) & 0xf; + sr_id = (frame_header[2] >> 2) & 3; + padding = (frame_header[2] >> 1) & 1; + mode = (frame_header[3] >> 6) & 3; + emphasis = (frame_header[3]) & 3; + + /* Check for invalid values */ + if (version == 3 || layer == 4 || br_id == 15 || sr_id == 3 || emphasis == 2) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + if (version == 4) version = 3; + + bitrate = mpga_bitrate[version == 1 ? 0 : 1][layer-1][br_id]; + bitrate *= 1000; + + sample_rate = mpga_sample_rate[sr_id]; + sample_rate >>= (version - 1); + + if (p_version) *p_version = version; + if (p_layer) *p_layer = layer; + if (p_sample_rate) *p_sample_rate = sample_rate; + if (p_channels) *p_channels = mode == MPGA_MODE_MONO ? 1 : 2; + if (p_frame_bitrate) *p_frame_bitrate = bitrate; + if (p_offset) *p_offset = 0; + + if (p_frame_size_samples) + { + *p_frame_size_samples = mpga_frame_size[layer - 1]; + if (version == 1 && layer == 3) *p_frame_size_samples <<= 1; + } + + if (!p_frame_size) + return VC_CONTAINER_SUCCESS; + + if (!bitrate) + *p_frame_size = 0; + else if (layer == 1) + *p_frame_size = (padding + bitrate * 12 / sample_rate) * 4; + else if (layer == 2) + *p_frame_size = padding + bitrate * 144 / sample_rate; + else + *p_frame_size = padding + bitrate * (version == 1 ? 144 : 72) / sample_rate; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T adts_read_header( uint8_t frame_header[MPGA_HEADER_SIZE], + uint32_t *p_frame_size, unsigned int *p_frame_bitrate, unsigned int *p_version, + unsigned int *p_layer, unsigned int *p_sample_rate, unsigned int *p_channels, + unsigned int *p_frame_size_samples, unsigned int *p_offset ) +{ + static const unsigned int adts_sample_rate[16] = + {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}; + unsigned int profile, sr_id, bitrate, sample_rate, frame_size, channels, crc; + unsigned int frame_size_samples = 1024; + + /* Check frame sync (12 bits) */ + if (frame_header[0] != 0xff || (frame_header[1] & 0xf0) != 0xf0) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + /* Layer must be 0 */ + if ((frame_header[1] >> 1) & 3) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + crc = !(frame_header[1] & 0x1); + profile = (frame_header[2] >> 6) + 1; /* MPEG-4 Audio Object Type */ + + sr_id = (frame_header[2] >> 2) & 0xf; + sample_rate = adts_sample_rate[sr_id]; + channels = ((frame_header[2] & 0x1) << 2) | ((frame_header[3] >> 6) & 0x3); + frame_size = ((frame_header[3] & 0x03) << 11) | (frame_header[4] << 3) | (frame_header[5] >> 5); + + if (!sample_rate || !channels || !frame_size) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + bitrate = frame_size * 8 * sample_rate / frame_size_samples; + + if (p_version) *p_version = profile; + if (p_layer) *p_layer = 0; + if (p_sample_rate) *p_sample_rate = sample_rate; + if (p_channels) *p_channels = channels; + if (p_frame_bitrate) *p_frame_bitrate = bitrate; + if (p_frame_size) *p_frame_size = frame_size; + if (p_frame_size_samples) *p_frame_size_samples = frame_size_samples; + if (p_offset) *p_offset = crc ? 9 : 7; + + return VC_CONTAINER_SUCCESS; +} diff --git a/containers/mpga/mpga_packetizer.c b/containers/mpga/mpga_packetizer.c new file mode 100644 index 0000000..1d7b7bf --- /dev/null +++ b/containers/mpga/mpga_packetizer.c @@ -0,0 +1,288 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** \file + * Implementation of an MPEG1/2/2.5 audio Layer I/II/III and AAC ADTS packetizer. + */ + +#include +#include + +#include "containers/packetizers.h" +#include "containers/core/packetizers_private.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_time.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_bytestream.h" +#include "mpga_common.h" + +#define MAX_FRAME_SIZE 2881 /* MPEG 2.5 Layer II, 8000 Hz, 160 kbps */ + +VC_CONTAINER_STATUS_T mpga_packetizer_open( VC_PACKETIZER_T * ); + +/*****************************************************************************/ +typedef struct VC_PACKETIZER_MODULE_T { + enum { + STATE_SYNC = 0, + STATE_SYNC_LOST, + STATE_SYNC_NEXT, + STATE_SYNC_DONE, + STATE_HEADER, + STATE_DATA, + } state; + + VC_CONTAINER_STATUS_T (*pf_read_header)( uint8_t frame_header[MPGA_HEADER_SIZE], + uint32_t *p_frame_size, unsigned int *p_frame_bitrate, unsigned int *p_version, + unsigned int *p_layer, unsigned int *p_sample_rate, unsigned int *p_channels, + unsigned int *p_frame_size_samples, unsigned int *p_offset); + + uint32_t frame_size; + unsigned int frame_bitrate; + unsigned int version; + unsigned int layer; + unsigned int sample_rate; + unsigned int channels; + unsigned int frame_size_samples; + unsigned int offset; + + unsigned int lost_sync; + + unsigned int stream_version; + unsigned int stream_layer; + + uint32_t bytes_read; + +} VC_PACKETIZER_MODULE_T; + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_packetizer_close( VC_PACKETIZER_T *p_ctx ) +{ + free(p_ctx->priv->module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_packetizer_reset( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + module->lost_sync = 0; + module->state = STATE_SYNC; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_packetizer_packetize( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_PACKET_T *out, VC_PACKETIZER_FLAGS_T flags ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_BYTESTREAM_T *stream = &p_ctx->priv->stream; + VC_CONTAINER_TIME_T *time = &p_ctx->priv->time; + uint8_t header[MPGA_HEADER_SIZE]; + VC_CONTAINER_STATUS_T status; + unsigned int version, layer; + int64_t pts, dts; + + while(1) switch (module->state) + { + case STATE_SYNC_LOST: + bytestream_skip_byte( stream ); + if( !module->lost_sync++ ) + LOG_DEBUG(0, "lost sync"); + module->state = STATE_SYNC; + + case STATE_SYNC: + while( bytestream_peek( stream, header, 2 ) == VC_CONTAINER_SUCCESS ) + { + /* 11 bits sync work (0xffe) */ + if( header[0] == 0xff && (header[1] & 0xe0) == 0xe0 ) + { + module->state = STATE_HEADER; + break; + } + bytestream_skip_byte( stream ); + module->lost_sync++; + } + if( module->state != STATE_HEADER ) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; /* We need more data */ + + case STATE_HEADER: + if( bytestream_peek( stream, header, MPGA_HEADER_SIZE ) != VC_CONTAINER_SUCCESS ) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + + status = mpga_read_header( header, + &module->frame_size, &module->frame_bitrate, &module->version, + &module->layer, &module->sample_rate, &module->channels, + &module->frame_size_samples, &module->offset ); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_ERROR(0, "invalid header"); + module->state = STATE_SYNC_LOST; + break; + } + + /* Version and layer are not allowed to change mid-stream */ + if ((module->stream_version && module->stream_version != module->version) || + (module->stream_layer && module->stream_layer != module->layer)) + { + LOG_ERROR(0, "invalid header"); + module->state = STATE_SYNC_LOST; + break; + } + /* We currently do not support free format streams */ + if (!module->frame_size) + { + LOG_ERROR(0, "free format not supported"); + module->state = STATE_SYNC_LOST; + break; + } + module->state = STATE_SYNC_NEXT; + /* fall through to the next state */ + + case STATE_SYNC_NEXT: + /* To avoid being caught by emulated start codes, we also look at where the next frame is supposed to be */ + if( bytestream_peek_at( stream, module->frame_size, header, MPGA_HEADER_SIZE ) != VC_CONTAINER_SUCCESS ) + { + /* If we know there won't be anymore data then we can just assume + * we've got the frame we're looking for */ + if (flags & VC_PACKETIZER_FLAG_FLUSH) + { + module->state = STATE_SYNC_DONE; + break; + } + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + } + + status = mpga_read_header( header, 0, 0, &version, &layer, 0, 0, 0, 0 ); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_ERROR(0, "invalid next header"); + module->state = STATE_SYNC_LOST; + break; + } + + /* Version and layer are not allowed to change mid-stream */ + if (module->version != version || module->layer != layer) + { + LOG_ERROR(0, "invalid header"); + module->state = STATE_SYNC_LOST; + break; + } + + module->state = STATE_SYNC_DONE; + /* fall through to the next state */ + + case STATE_SYNC_DONE: + if( module->lost_sync ) + LOG_DEBUG(0, "recovered sync after %i bytes", module->lost_sync); + module->lost_sync = 0; + + bytestream_skip( stream, module->offset ); + module->stream_version = module->version; + module->stream_layer = module->layer; + + vc_container_time_set_samplerate(time, module->sample_rate, 1); + bytestream_get_timestamps(stream, &pts, &dts, true); + + vc_container_time_set(time, pts); + + module->bytes_read = 0; + module->state = STATE_DATA; + /* fall through to the next state */ + + case STATE_DATA: + if( bytestream_size( stream ) < module->frame_size) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + + out->size = module->frame_size - module->bytes_read; + out->pts = out->dts = VC_CONTAINER_TIME_UNKNOWN; + out->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END; + + if(!module->bytes_read) + { + out->pts = out->dts = vc_container_time_get(time); + out->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + } + + if(flags & VC_PACKETIZER_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + if(flags & VC_PACKETIZER_FLAG_SKIP) + { + bytestream_skip( stream, out->size ); + } + else + { + out->size = MIN(out->size, out->buffer_size); + bytestream_get( stream, out->data, out->size ); + } + module->bytes_read += out->size; + + if(module->bytes_read == module->frame_size) + { + vc_container_time_add(time, module->frame_size_samples); + module->state = STATE_HEADER; + } + return VC_CONTAINER_SUCCESS; + + default: + break; + }; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T mpga_packetizer_open( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module; + + if(p_ctx->in->codec != VC_CONTAINER_CODEC_MPGA && + p_ctx->in->codec != VC_CONTAINER_CODEC_MP4A) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + p_ctx->priv->module = module = malloc(sizeof(*module)); + if(!module) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + memset(module, 0, sizeof(*module)); + + if(p_ctx->in->codec == VC_CONTAINER_CODEC_MPGA) + module->pf_read_header = mpga_read_header; + else + module->pf_read_header = adts_read_header; + + vc_container_format_copy( p_ctx->out, p_ctx->in, 0); + p_ctx->max_frame_size = MAX_FRAME_SIZE; + p_ctx->priv->pf_close = mpga_packetizer_close; + p_ctx->priv->pf_packetize = mpga_packetizer_packetize; + p_ctx->priv->pf_reset = mpga_packetizer_reset; + LOG_DEBUG(0, "using mpeg audio packetizer"); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_PACKETIZER_REGISTER(mpga_packetizer_open, "mpga"); diff --git a/containers/mpga/mpga_reader.c b/containers/mpga/mpga_reader.c new file mode 100644 index 0000000..da5df83 --- /dev/null +++ b/containers/mpga/mpga_reader.c @@ -0,0 +1,618 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#define CONTAINER_IS_BIG_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#define CONTAINER_HELPER_LOG_INDENT(a) 0 +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" +#include "mpga_common.h" + +/****************************************************************************** +Defines and constants. +******************************************************************************/ +#define MPGA_XING_HAS_FRAMES 0x00000001 +#define MPGA_XING_HAS_BYTES 0x00000002 +#define MPGA_XING_HAS_TOC 0x00000004 +#define MPGA_XING_HAS_QUALITY 0x00000008 + +#define MPGA_MAX_BAD_FRAMES 4096 /*< Maximum number of failed byte-wise syncs, + should be at least 2881+4 to cover the largest + frame size (MPEG2.5 Layer 2, 160kbit/s 8kHz) + + next frame header */ + +static const unsigned int mpga_sample_rate_adts[16] = +{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}; + +static const GUID_T asf_guid_header = +{0x75B22630, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}}; + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + uint64_t data_offset; + uint64_t data_size; + uint64_t num_frames; /**< Total number of frames (if known) */ + unsigned int frame_size_samples; /**< Frame size in samples */ + unsigned int bitrate; /**< Bitrate (might change on a per-frame basis if VBR) */ + unsigned int sample_rate; + unsigned int channels; + + /* MPEG audio header information */ + unsigned int version; /**< 1 for MPEG1, 2 for MPEG2, etc. */ + unsigned int layer; + + /* VBR header information */ + uint8_t xing_toc[100]; + int xing_toc_valid; + + /* Per-frame state (updated upon a read or a seek) */ + unsigned int frame_size; + unsigned int frame_data_left; + uint64_t frame_index; + int64_t frame_offset; + int64_t frame_time_pos; /**< pts of current frame */ + unsigned int frame_bitrate; /**< bitrate of current frame */ + + VC_CONTAINER_STATUS_T (*pf_parse_header)( uint8_t frame_header[MPGA_HEADER_SIZE], + uint32_t *p_frame_size, unsigned int *p_frame_bitrate, unsigned int *p_version, + unsigned int *p_layer, unsigned int *p_sample_rate, unsigned int *p_channels, + unsigned int *p_frame_size_samples, unsigned int *p_offset); + + uint8_t extradata[2]; /**< codec extra data for aac */ + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T mpga_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ +static uint32_t PEEK_BYTES_AT( VC_CONTAINER_T *p_ctx, int64_t offset, uint8_t *buffer, int size ) +{ + int ret; + int64_t current_position = STREAM_POSITION(p_ctx); + SEEK(p_ctx, current_position + offset); + ret = PEEK_BYTES(p_ctx, buffer, size); + SEEK(p_ctx, current_position); + return ret; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_check_frame_header( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_MODULE_T *module, uint8_t frame_header[MPGA_HEADER_SIZE] ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + return module->pf_parse_header(frame_header, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_sync( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + uint8_t frame_header[MPGA_HEADER_SIZE]; + uint32_t frame_size; + unsigned int frame_bitrate, version, layer, sample_rate, channels; + unsigned int frame_size_samples, offset; + int sync_count = 0; + + /* If we can't see a full frame header, we treat this as EOS although it + could be a bad stream as well, the caller should distinct between + these two cases */ + if (PEEK_BYTES(p_ctx, (uint8_t*)frame_header, MPGA_HEADER_SIZE) != MPGA_HEADER_SIZE) + return VC_CONTAINER_ERROR_EOS; + + while (sync_count++ < MPGA_MAX_BAD_FRAMES) + { + status = module->pf_parse_header(frame_header, &frame_size, &frame_bitrate, + &version, &layer, &sample_rate, &channels, + &frame_size_samples, &offset); + if (status == VC_CONTAINER_SUCCESS && + frame_size /* We do not support free format streams */) + { + LOG_DEBUG(p_ctx, "MPEGv%d, layer %d, %d bps, %d Hz", + version, layer, frame_bitrate, sample_rate); + if (PEEK_BYTES_AT(p_ctx, (int64_t)frame_size, frame_header, MPGA_HEADER_SIZE) != MPGA_HEADER_SIZE || + mpga_check_frame_header(p_ctx, module, frame_header) == VC_CONTAINER_SUCCESS) + break; + + /* If we've reached an ID3 tag then the frame is valid as well */ + if((frame_header[0] == 'I' && frame_header[1] == 'D' && frame_header[2] == '3') || + (frame_header[0] == 'T' && frame_header[1] == 'A' && frame_header[2] == 'G')) + break; + } + else if (status == VC_CONTAINER_SUCCESS) + { + LOG_DEBUG(p_ctx, "free format not supported"); + } + + if (SKIP_BYTES(p_ctx, 1) != 1 || PEEK_BYTES(p_ctx, (uint8_t*)frame_header, MPGA_HEADER_SIZE) != MPGA_HEADER_SIZE) + return VC_CONTAINER_ERROR_EOS; + } + + if(sync_count > MPGA_MAX_BAD_FRAMES) /* We didn't find a valid frame */ + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + if (module->version) + { + /* FIXME: we don't currently care whether or not the number of channels changes mid-stream */ + if (version != module->version || layer != module->layer) + { + LOG_DEBUG(p_ctx, "version or layer not allowed to change mid-stream"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + } + else + { + module->version = version; + module->layer = layer; + module->sample_rate = sample_rate; + module->channels = channels; + module->frame_size_samples = frame_size_samples; + } + + if(offset) SKIP_BYTES(p_ctx, offset); + module->frame_data_left = module->frame_size = frame_size - offset; + module->frame_bitrate = frame_bitrate; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static int64_t mpga_calculate_frame_time( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + int64_t time; + time = INT64_C(1000000) * module->frame_index * + module->frame_size_samples / module->sample_rate; + return time; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_read_vbr_headers( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[0]; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND; + uint32_t peek_buf[1]; + int64_t offset, start = STREAM_POSITION(p_ctx); + + /* Look for XING header (immediately after layer 3 side information) */ + offset = (module->version == 1) ? ((module->channels == 1) ? INT64_C(21) : INT64_C(36)) : + ((module->channels == 1) ? INT64_C(13) : INT64_C(21)); + + if (PEEK_BYTES_AT(p_ctx, offset, (uint8_t*)peek_buf, 4) != 4) + return VC_CONTAINER_ERROR_FORMAT_INVALID; /* File would be way too small */ + + if (peek_buf[0] == VC_FOURCC('X','i','n','g') || peek_buf[0] == VC_FOURCC('I','n','f','o')) + { + uint32_t flags = 0, num_frames = 0, data_size = 0; + + /* If the first frame has a XING header then we know it's a valid (but empty) audio + frame so we safely parse the header whilst skipping to the next frame */ + SKIP_BYTES(p_ctx, offset); /* FIXME: we don't care about layer 3 side information? */ + + SKIP_FOURCC(p_ctx, "XING"); + flags = READ_U32(p_ctx, "XING flags"); + + if (flags & MPGA_XING_HAS_FRAMES) + num_frames = READ_U32(p_ctx, "XING frames"); + + if (flags & MPGA_XING_HAS_BYTES) + data_size = READ_U32(p_ctx, "XING bytes"); + + if (flags & MPGA_XING_HAS_TOC) + { + READ_BYTES(p_ctx, module->xing_toc, sizeof(module->xing_toc)); + /* TOC is useful only if we know the number of frames */ + if (num_frames) module->xing_toc_valid = 1; + /* Ensure time zero points to first frame even if TOC is broken */ + module->xing_toc[0] = 0; + } + + if (flags & MPGA_XING_HAS_QUALITY) + SKIP_U32(p_ctx, "XING quality"); + + module->data_size = data_size; + module->num_frames = num_frames; + + if (module->num_frames && module->data_size) + { + /* We can calculate average bitrate */ + module->bitrate = + module->data_size * module->sample_rate * 8 / (module->num_frames * module->frame_size_samples); + } + + p_ctx->duration = (module->num_frames * module->frame_size_samples * 1000000LL) / module->sample_rate; + + /* Look for additional LAME header (follows XING) */ + if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4) + return VC_CONTAINER_ERROR_FORMAT_INVALID; /* File would still be way too small */ + + if (peek_buf[0] == VC_FOURCC('L','A','M','E')) + { + uint32_t encoder_delay; + + SKIP_FOURCC(p_ctx, "LAME"); + SKIP_STRING(p_ctx, 5, "LAME encoder version"); + SKIP_U8(p_ctx, "LAME tag revision/VBR method"); + SKIP_U8(p_ctx, "LAME LP filter value"); + SKIP_U32(p_ctx, "LAME peak signal amplitude"); + SKIP_U16(p_ctx, "LAME radio replay gain"); + SKIP_U16(p_ctx, "LAME audiophile replay gain"); + SKIP_U8(p_ctx, "LAME encoder flags"); + SKIP_U8(p_ctx, "LAME ABR/minimal bitrate"); + encoder_delay = READ_U24(p_ctx, "LAME encoder delay/padding"); + SKIP_U8(p_ctx, "LAME misc"); + SKIP_U8(p_ctx, "LAME MP3 gain"); + SKIP_U16(p_ctx, "LAME presets and surround info"); + SKIP_U32(p_ctx, "LAME music length"); + SKIP_U16(p_ctx, "LAME music CRC"); + SKIP_U16(p_ctx, "LAME tag CRC"); + track->format->type->audio.gap_delay = (encoder_delay >> 12) + module->frame_size_samples; + track->format->type->audio.gap_padding = encoder_delay & 0xfff; + } + + SEEK(p_ctx, start); + status = VC_CONTAINER_SUCCESS; + } + + /* FIXME: if not success, try to read 'VBRI' header */ + + return status; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[0]; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + if (module->frame_data_left == 0) + { + status = mpga_sync(p_ctx); + if (status != VC_CONTAINER_SUCCESS) goto error; + } + + if (module->bitrate) + { + /* Simple moving average over bitrate values seen so far */ + module->bitrate = (module->bitrate * 31 + module->frame_bitrate) >> 5; + } + else + { + module->bitrate = module->frame_bitrate; + } + + /* Check if we can skip the frame straight-away */ + if (!track->is_enabled || + ((flags & VC_CONTAINER_READ_FLAG_SKIP) && !(flags & VC_CONTAINER_READ_FLAG_INFO))) + { + /* Just skip the frame */ + SKIP_BYTES(p_ctx, module->frame_size); + module->frame_data_left = 0; + if(!track->is_enabled) + status = VC_CONTAINER_ERROR_CONTINUE; + goto end; + } + + /* Fill in packet information */ + p_packet->flags = p_packet->track = 0; + if (module->frame_data_left == module->frame_size) + p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME; + else + p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + + p_packet->size = module->frame_data_left; + + p_packet->pts = module->frame_time_pos; + p_packet->dts = VC_CONTAINER_TIME_UNKNOWN; + + if ((flags & VC_CONTAINER_READ_FLAG_SKIP)) + { + SKIP_BYTES(p_ctx, module->frame_size); + module->frame_data_left = 0; + goto end; + } + + if (flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + p_packet->size = MIN(p_packet->buffer_size, module->frame_data_left); + p_packet->size = READ_BYTES(p_ctx, p_packet->data, p_packet->size); + module->frame_data_left -= p_packet->size; + + end: + if (module->frame_data_left == 0) + { + module->frame_index++; + module->frame_offset += module->frame_size; + module->frame_time_pos = mpga_calculate_frame_time(p_ctx); + +#if 0 /* FIXME: is this useful e.g. progressive download? */ + module->num_frames = MAX(module->num_frames, module->frame_index); + module->data_size = MAX(module->data_size, module->frame_offset); + p_ctx->duration = MAX(p_ctx->duration, mpga_calculate_frame_time(p_ctx)); +#endif + } + + return status == VC_CONTAINER_SUCCESS ? STREAM_STATUS(p_ctx) : status; + +error: + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_reader_seek( VC_CONTAINER_T *p_ctx, + int64_t *p_offset, + VC_CONTAINER_SEEK_MODE_T mode, + VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint64_t seekpos, position = STREAM_POSITION(p_ctx); + VC_CONTAINER_PARAM_UNUSED(flags); + + if (mode != VC_CONTAINER_SEEK_MODE_TIME || !STREAM_SEEKABLE(p_ctx)) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + if (*p_offset != INT64_C(0)) + { + if (!p_ctx->duration) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + if (module->xing_toc_valid) + { + int64_t ppm; + int percent, lower, upper, delta; + + ppm = (*p_offset * module->sample_rate) / (module->num_frames * module->frame_size_samples); + ppm = MIN(ppm, INT64_C(999999)); + + percent = ppm / 10000; + delta = ppm % 10000; + + lower = module->xing_toc[percent]; + upper = percent < 99 ? module->xing_toc[percent + 1] : 256; + + seekpos = module->data_offset + + (((module->data_size * lower) + (module->data_size * (upper - lower) * delta) / 10000) >> 8); + } + else + { + /* The following will be accurate for CBR only */ + seekpos = module->data_offset + (*p_offset * module->data_size) / p_ctx->duration; + } + } + else + { + seekpos = module->data_offset; + } + + SEEK(p_ctx, seekpos); + status = mpga_sync(p_ctx); + if (status && status != VC_CONTAINER_ERROR_EOS) + goto error; + + module->frame_index = (*p_offset * module->num_frames + (p_ctx->duration >> 1)) / p_ctx->duration; + module->frame_offset = STREAM_POSITION(p_ctx) - module->data_offset; + + *p_offset = module->frame_time_pos = mpga_calculate_frame_time(p_ctx); + + return STREAM_STATUS(p_ctx); + +error: + SEEK(p_ctx, position); + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpga_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + if (p_ctx->tracks_num != 0) + vc_container_free_track(p_ctx, p_ctx->tracks[0]); + p_ctx->tracks = NULL; + p_ctx->tracks_num = 0; + free(module); + p_ctx->priv->module = 0; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T mpga_reader_open( VC_CONTAINER_T *p_ctx ) +{ + const char *extension = vc_uri_path_extension(p_ctx->priv->uri); + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_TRACK_T *track = NULL; + unsigned int i; + GUID_T guid; + + /* Check if the user has specified a container */ + vc_uri_find_query(p_ctx->priv->uri, 0, "container", &extension); + + /* Since mpeg audio is difficult to auto-detect, we use the extension as + part of the autodetection */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(strcasecmp(extension, "mp3") && strcasecmp(extension, "mp2") && + strcasecmp(extension, "aac") && strcasecmp(extension, "adts")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Check we're not in fact dealing with an ASF file */ + if(PEEK_BYTES(p_ctx, (uint8_t *)&guid, sizeof(guid)) == sizeof(guid) && + !memcmp(&guid, &asf_guid_header, sizeof(guid))) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + LOG_DEBUG(p_ctx, "using mpga reader"); + + /* Allocate our context */ + if ((module = malloc(sizeof(*module))) == NULL) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto error; + } + + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = &module->track; + + p_ctx->tracks[0] = vc_container_allocate_track(p_ctx, 0); + if(!p_ctx->tracks[0]) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto error; + } + p_ctx->tracks_num = 1; + + module->pf_parse_header = mpga_read_header; + if(!strcasecmp(extension, "aac") || !strcasecmp(extension, "adts")) + module->pf_parse_header = adts_read_header; + + if ((status = mpga_sync(p_ctx)) != VC_CONTAINER_SUCCESS) + { + /* An error here probably means it's not an mpga file at all */ + if(status == VC_CONTAINER_ERROR_FORMAT_INVALID) + status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + goto error; + } + + /* If we got this far, we're probably dealing with an mpeg audio file */ + track = p_ctx->tracks[0]; + track->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + track->format->codec = VC_CONTAINER_CODEC_MPGA; + if(module->pf_parse_header == adts_read_header) + { + uint8_t *extra = track->format->extradata = module->extradata; + unsigned int sr_id; + for( sr_id = 0; sr_id < 13; sr_id++ ) + if( mpga_sample_rate_adts[sr_id] == module->sample_rate ) break; + extra[0] = (module->version << 3) | ((sr_id & 0xe) >> 1); + extra[1] = ((sr_id & 0x1) << 7) | (module->channels << 3); + track->format->extradata_size = 2; + track->format->codec = VC_CONTAINER_CODEC_MP4A; + } + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + track->is_enabled = true; + track->format->type->audio.channels = module->channels; + track->format->type->audio.sample_rate = module->sample_rate; + track->format->type->audio.bits_per_sample = 0; + track->format->type->audio.block_align = 1; + + module->data_offset = STREAM_POSITION(p_ctx); + + /* Look for VBR headers within the first frame */ + status = mpga_read_vbr_headers(p_ctx); + if (status && status != VC_CONTAINER_ERROR_NOT_FOUND) goto error; + + /* If we couldn't get this information from VBR headers, try to determine + file size, bitrate, number of frames and duration */ + if (!module->data_size) + module->data_size = MAX(p_ctx->priv->io->size - module->data_offset, INT64_C(0)); + + if (!module->bitrate) + { + if (STREAM_SEEKABLE(p_ctx)) + { + /* Scan past a few hundred frames (audio will often have + silence in the beginning so we need to see more than + just a few frames) and estimate bitrate */ + for (i = 0; i < 256; ++i) + if (mpga_reader_read(p_ctx, NULL, VC_CONTAINER_READ_FLAG_SKIP)) break; + /* Seek back to start of data */ + SEEK(p_ctx, module->data_offset); + module->frame_index = 0; + module->frame_offset = INT64_C(0); + module->frame_time_pos = mpga_calculate_frame_time(p_ctx); + } + else + { + /* Bitrate will be correct for CBR only */ + module->bitrate = module->frame_bitrate; + } + } + + track->format->bitrate = module->bitrate; + + if (!module->num_frames) + { + module->num_frames = (module->data_size * module->sample_rate * 8LL) / + (module->bitrate * module->frame_size_samples); + } + + if (!p_ctx->duration && module->bitrate) + { + p_ctx->duration = (INT64_C(8000000) * module->data_size) / module->bitrate; + } + + p_ctx->priv->pf_close = mpga_reader_close; + p_ctx->priv->pf_read = mpga_reader_read; + p_ctx->priv->pf_seek = mpga_reader_seek; + + if(STREAM_SEEKABLE(p_ctx)) p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) goto error; + return VC_CONTAINER_SUCCESS; + +error: + if(status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_EOS) + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + LOG_DEBUG(p_ctx, "error opening stream (%i)", status); + if (p_ctx->tracks_num != 0) + vc_container_free_track(p_ctx, p_ctx->tracks[0]); + p_ctx->tracks = NULL; + p_ctx->tracks_num = 0; + if (module) free(module); + p_ctx->priv->module = NULL; + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open mpga_reader_open +#endif diff --git a/containers/mpgv/mpgv_packetizer.c b/containers/mpgv/mpgv_packetizer.c new file mode 100644 index 0000000..79ccf78 --- /dev/null +++ b/containers/mpgv/mpgv_packetizer.c @@ -0,0 +1,431 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** \file + * Implementation of an MPEG1/2 video packetizer. + */ + +#include +#include + +#include "containers/packetizers.h" +#include "containers/core/packetizers_private.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_time.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_bytestream.h" + +/** Arbitrary number which should be sufficiently high so that no sane frame will + * be bigger than that. */ +#define MAX_FRAME_SIZE (1920*1088*2) + +static uint8_t mpgv_startcode[3] = {0x0, 0x0, 0x1}; + +#define PICTURE_CODING_TYPE_I 0x1 +#define PICTURE_CODING_TYPE_P 0x2 +#define PICTURE_CODING_TYPE_B 0x3 + +VC_CONTAINER_STATUS_T mpgv_packetizer_open( VC_PACKETIZER_T * ); + +/*****************************************************************************/ +typedef struct VC_PACKETIZER_MODULE_T { + enum { + STATE_SYNC = 0, + STATE_SYNC_NEXT, + STATE_FRAME_DONE, + STATE_UNIT_HEADER, + STATE_UNIT_SEQUENCE, + STATE_UNIT_GROUP, + STATE_UNIT_PICTURE, + STATE_UNIT_SLICE, + STATE_UNIT_OTHER, + STATE_DATA, + } state; + + size_t frame_size; + size_t unit_offset; + + unsigned int seen_sequence_header; + unsigned int seen_picture_header; + unsigned int seen_slice; + unsigned int lost_sync; + + unsigned int picture_type; + unsigned int picture_temporal_ref; + int64_t pts; + int64_t dts; + + uint32_t bytes_read; + + unsigned int width, height; + unsigned int frame_rate_num, frame_rate_den; + unsigned int aspect_ratio_num, aspect_ratio_den; + bool low_delay; + +} VC_PACKETIZER_MODULE_T; + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpgv_packetizer_close( VC_PACKETIZER_T *p_ctx ) +{ + free(p_ctx->priv->module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpgv_packetizer_reset( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + module->lost_sync = 0; + module->state = STATE_SYNC; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpgv_read_sequence_header(VC_CONTAINER_BYTESTREAM_T *stream, + size_t offset, unsigned int *width, unsigned int *height, + unsigned int *frame_rate_num, unsigned int *frame_rate_den, + unsigned int *aspect_ratio_num, unsigned int *aspect_ratio_den) +{ + static const int frame_rate[16][2] = + { {0, 0}, {24000, 1001}, {24, 1}, {25, 1}, {30000, 1001}, {30, 1}, {50, 1}, + {60000, 1001}, {60, 1}, + /* Unofficial values */ + {15, 1001}, /* From Xing */ + {5, 1001}, {10, 1001}, {12, 1001}, {15, 1001} /* From libmpeg3 */ }; + static const int aspect_ratio[16][2] = + { {0, 0}, {1, 1}, {4, 3}, {16, 9}, {221, 100} }; + + VC_CONTAINER_STATUS_T status; + unsigned int w, h, fr, ar; + int64_t ar_num, ar_den, div; + uint8_t header[8]; + + status = bytestream_peek_at( stream, offset, header, sizeof(header)); + if(status != VC_CONTAINER_SUCCESS) + return status; + + w = (header[4] << 4) | (header[5] >> 4); + h = ((header[5]&0x0f) << 8) | header[6]; + ar = header[7] >> 4; + fr = header[7]&0x0f; + if (!w || !h || !ar || !fr) + return VC_CONTAINER_ERROR_CORRUPTED; + + *width = w; + *height = h; + *frame_rate_num = frame_rate[fr][0]; + *frame_rate_den = frame_rate[fr][1]; + ar_num = (int64_t)aspect_ratio[ar][0] * h; + ar_den = (int64_t)aspect_ratio[ar][1] * w; + div = vc_container_maths_gcd(ar_num, ar_den); + if (div) + { + ar_num /= div; + ar_den /= div; + } + *aspect_ratio_num = ar_num; + *aspect_ratio_den = ar_den; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpgv_read_picture_header(VC_CONTAINER_BYTESTREAM_T *stream, + size_t offset, unsigned int *type, unsigned int *temporal_ref) +{ + VC_CONTAINER_STATUS_T status; + uint8_t h[2]; + + status = bytestream_peek_at(stream, offset + sizeof(mpgv_startcode) + 1, h, sizeof(h)); + if(status != VC_CONTAINER_SUCCESS) + return status; + + *temporal_ref = (h[0] << 2) | (h[1] >> 6); + *type = (h[1] >> 3) & 0x7; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpgv_update_format( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + + LOG_DEBUG(0, "mpgv format: width %i, height %i, rate %i/%i, ar %i/%i", + module->width, module->height, module->frame_rate_num, module->frame_rate_den, + module->aspect_ratio_num, module->aspect_ratio_den); + + p_ctx->out->type->video.width = p_ctx->out->type->video.visible_width = module->width; + p_ctx->out->type->video.height = p_ctx->out->type->video.visible_height = module->height; + p_ctx->out->type->video.par_num = module->aspect_ratio_num; + p_ctx->out->type->video.par_den = module->aspect_ratio_den; + p_ctx->out->type->video.frame_rate_num = module->frame_rate_num; + p_ctx->out->type->video.frame_rate_den = module->frame_rate_den; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T mpgv_packetizer_packetize( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_PACKET_T *out, VC_PACKETIZER_FLAGS_T flags) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_BYTESTREAM_T *stream = &p_ctx->priv->stream; + VC_CONTAINER_TIME_T *time = &p_ctx->priv->time; + VC_CONTAINER_STATUS_T status; + uint8_t header[4]; + size_t offset; + + while(1) switch (module->state) + { + case STATE_SYNC: + offset = 0; + status = bytestream_find_startcode( stream, &offset, + mpgv_startcode, sizeof(mpgv_startcode) ); + + if(offset && !module->lost_sync) + LOG_DEBUG(0, "lost sync"); + + bytestream_skip(stream, offset); + module->lost_sync += offset; + + if(status != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; /* We need more data */ + + if(module->lost_sync) + LOG_DEBUG(0, "recovered sync after %i bytes", module->lost_sync); + module->lost_sync = 0; + module->state = STATE_UNIT_HEADER; + module->frame_size = 0; + module->unit_offset = 0; + /* fall through to the next state */ + + case STATE_UNIT_HEADER: + status = bytestream_peek_at( stream, module->unit_offset, header, sizeof(header)); + if(status != VC_CONTAINER_SUCCESS) + { + if (!(flags & VC_PACKETIZER_FLAG_FLUSH) || + !module->seen_picture_header || !module->seen_slice) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + module->state = STATE_FRAME_DONE; + break; + } + +#if defined(ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE) + LOG_DEBUG(0, "found unit (%x)", header[3]); +#endif + + /* Detect start of new frame */ + if(module->seen_picture_header && module->seen_slice && + (header[3] == 0x00 /* A picture header */ || + (header[3] > 0xAF && header[3] != 0xB7) /* Not a slice or sequence end */)) + { + module->state = STATE_FRAME_DONE; + break; + } + + module->frame_size += sizeof(mpgv_startcode); + module->state = STATE_SYNC_NEXT; + /* fall through to the next state */ + + case STATE_SYNC_NEXT: + status = bytestream_find_startcode( stream, &module->frame_size, + mpgv_startcode, sizeof(mpgv_startcode) ); + + /* Sanity check the size of frames. This makes sure we don't endlessly accumulate data + * to make up a new frame. */ + if(module->frame_size > p_ctx->max_frame_size) + { + LOG_ERROR(0, "frame too big (%i/%i), dropping", module->frame_size, p_ctx->max_frame_size); + bytestream_skip(stream, module->frame_size); + module->state = STATE_SYNC; + break; + } + if(status != VC_CONTAINER_SUCCESS) + { + if (!(flags & VC_PACKETIZER_FLAG_FLUSH) || + !module->seen_picture_header || !module->seen_slice) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + module->state = STATE_FRAME_DONE; + break; + } + + bytestream_peek_at( stream, module->unit_offset, header, sizeof(header)); + + /* Drop everything until we've seen a sequence header */ + if(header[3] != 0xB3 && !module->seen_sequence_header) + { + LOG_DEBUG(0, "waiting for sequence header, dropping %i bytes", module->frame_size); + module->state = STATE_UNIT_HEADER; + bytestream_skip(stream, module->frame_size); + module->unit_offset = module->frame_size = 0; + break; + } + + if(header[3] == 0x00) + module->state = STATE_UNIT_PICTURE; + else if(header[3] >= 0x01 && header[3] <= 0xAF) + module->state = STATE_UNIT_SLICE; + else if(header[3] == 0xB3) + module->state = STATE_UNIT_SEQUENCE; + else if(header[3] == 0xB8) + module->state = STATE_UNIT_GROUP; + else + module->state = STATE_UNIT_OTHER; + break; + + case STATE_UNIT_SEQUENCE: + status = mpgv_read_sequence_header(stream, module->unit_offset, &module->width, &module->height, + &module->frame_rate_num, &module->frame_rate_den, &module->aspect_ratio_num, &module->aspect_ratio_den); + if(status != VC_CONTAINER_SUCCESS && !module->seen_sequence_header) + { + /* We need a sequence header so drop everything until we see one */ + LOG_DEBUG(0, "invalid first sequence header, dropping %i bytes", module->frame_size); + bytestream_skip(stream, module->frame_size); + module->state = STATE_SYNC; + break; + } + mpgv_update_format(p_ctx); + module->seen_sequence_header = true; + vc_container_time_set_samplerate(time, module->frame_rate_num, module->frame_rate_den); + module->state = STATE_UNIT_HEADER; + module->unit_offset = module->frame_size; + break; + + case STATE_UNIT_PICTURE: + status = mpgv_read_picture_header(stream, module->unit_offset, &module->picture_type, &module->picture_temporal_ref); + if(status != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + module->seen_picture_header = true; + module->state = STATE_UNIT_HEADER; + module->unit_offset = module->frame_size; + break; + + case STATE_UNIT_SLICE: + module->seen_slice = true; + module->state = STATE_UNIT_HEADER; + module->unit_offset = module->frame_size; + break; + + case STATE_UNIT_GROUP: + case STATE_UNIT_OTHER: + module->state = STATE_UNIT_HEADER; + module->unit_offset = module->frame_size; + break; + + case STATE_FRAME_DONE: + bytestream_get_timestamps(stream, &module->pts, &module->dts, false); + + if(module->picture_type == PICTURE_CODING_TYPE_B || module->low_delay) + { + if(module->pts == VC_CONTAINER_TIME_UNKNOWN) + module->pts = module->dts; + if(module->dts == VC_CONTAINER_TIME_UNKNOWN) + module->dts = module->pts; + } + vc_container_time_set(time, module->pts); + + module->bytes_read = 0; + module->state = STATE_DATA; + module->seen_slice = false; + module->seen_picture_header = false; + +#if defined(ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE) + LOG_DEBUG(0, "new frame, type %x, size %i, temp_ref %i)", module->picture_type, + module->frame_size, module->picture_temporal_ref); +#endif + /* fall through to the next state */ + + case STATE_DATA: + out->size = module->frame_size - module->bytes_read; + out->pts = out->dts = VC_CONTAINER_TIME_UNKNOWN; + out->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END; + + if(!module->bytes_read) + { + out->pts = module->pts; + out->dts = module->dts; + out->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + } + + if(flags & VC_PACKETIZER_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + if(flags & VC_PACKETIZER_FLAG_SKIP) + { + bytestream_skip( stream, out->size ); + } + else + { + out->size = MIN(out->size, out->buffer_size); + bytestream_get( stream, out->data, out->size ); + } + module->bytes_read += out->size; + + if(module->bytes_read == module->frame_size) + { + vc_container_time_add(time, 1); + module->state = STATE_UNIT_HEADER; + module->frame_size = 0; + module->unit_offset = 0; + } + else + out->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + + return VC_CONTAINER_SUCCESS; + + default: + break; + }; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T mpgv_packetizer_open( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module; + + if(p_ctx->in->codec != VC_CONTAINER_CODEC_MP1V && + p_ctx->in->codec != VC_CONTAINER_CODEC_MP2V) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + p_ctx->priv->module = module = malloc(sizeof(*module)); + if(!module) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + memset(module, 0, sizeof(*module)); + + vc_container_format_copy( p_ctx->out, p_ctx->in, 0); + p_ctx->max_frame_size = MAX_FRAME_SIZE; + p_ctx->priv->pf_close = mpgv_packetizer_close; + p_ctx->priv->pf_packetize = mpgv_packetizer_packetize; + p_ctx->priv->pf_reset = mpgv_packetizer_reset; + LOG_DEBUG(0, "using mpeg video packetizer"); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_PACKETIZER_REGISTER(mpgv_packetizer_open, "mpgv"); diff --git a/containers/net/net_sockets.h b/containers/net/net_sockets.h new file mode 100644 index 0000000..f8451f3 --- /dev/null +++ b/containers/net/net_sockets.h @@ -0,0 +1,259 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VC_NET_SOCKETS_H +#define VC_NET_SOCKETS_H + +/** \file net_sockets.h + * Abstraction layer for socket-style network communication, to enable porting + * between platforms. + * + * Does not support IPv6 multicast. + */ + +#include "containers/containers_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Status codes that can occur in a socket instance. */ +typedef enum { + VC_CONTAINER_NET_SUCCESS = 0, /**< No error */ + VC_CONTAINER_NET_ERROR_GENERAL, /**< An unrecognised error has occurred */ + VC_CONTAINER_NET_ERROR_INVALID_SOCKET, /**< Invalid socket passed to function */ + VC_CONTAINER_NET_ERROR_NOT_ALLOWED, /**< The operation requested is not allowed */ + VC_CONTAINER_NET_ERROR_INVALID_PARAMETER, /**< An invalid parameter was passed in */ + VC_CONTAINER_NET_ERROR_NO_MEMORY, /**< Failure due to lack of memory */ + VC_CONTAINER_NET_ERROR_ACCESS_DENIED, /**< Permission denied */ + VC_CONTAINER_NET_ERROR_TOO_BIG, /**< Too many handles already open */ + VC_CONTAINER_NET_ERROR_WOULD_BLOCK, /**< Asynchronous operation would block */ + VC_CONTAINER_NET_ERROR_IN_PROGRESS, /**< An operation is already in progress on this socket */ + VC_CONTAINER_NET_ERROR_IN_USE, /**< The address/port is already in use */ + VC_CONTAINER_NET_ERROR_NETWORK, /**< Network is unavailable */ + VC_CONTAINER_NET_ERROR_CONNECTION_LOST, /**< The connection has been lost, closed by network, etc. */ + VC_CONTAINER_NET_ERROR_NOT_CONNECTED, /**< The socket is not connected */ + VC_CONTAINER_NET_ERROR_TIMED_OUT, /**< Operation timed out */ + VC_CONTAINER_NET_ERROR_CONNECTION_REFUSED, /**< Connection was refused by target */ + VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND, /**< Target address could not be resolved */ + VC_CONTAINER_NET_ERROR_TRY_AGAIN, /**< A temporary failure occurred that may clear */ +} vc_container_net_status_t; + +/** Operations that can be applied to sockets */ +typedef enum { + /** Set the buffer size used on the socket + * arg1: uint32_t - New buffer size in bytes */ + VC_CONTAINER_NET_CONTROL_SET_READ_BUFFER_SIZE = 1, + /** Set the timeout to be used on read operations + * arg1: uint32_t - New timeout in milliseconds, or INFINITE_TIMEOUT_MS */ + VC_CONTAINER_NET_CONTROL_SET_READ_TIMEOUT_MS, +} vc_container_net_control_t; + +/** Container Input / Output Context. + * This is an opaque structure that defines the context for a socket instance. + * The details of the structure are contained within the platform implementation. */ +typedef struct vc_container_net_tag VC_CONTAINER_NET_T; + +/** \name Socket open flags + * The following flags can be used when opening a network socket. */ +/* @{ */ +typedef uint32_t vc_container_net_open_flags_t; +/** Connected stream socket, rather than connectionless datagram socket */ +#define VC_CONTAINER_NET_OPEN_FLAG_STREAM 1 +/** Force use of IPv4 addressing */ +#define VC_CONTAINER_NET_OPEN_FLAG_FORCE_IP4 2 +/** Force use of IPv6 addressing */ +#define VC_CONTAINER_NET_OPEN_FLAG_FORCE_IP6 6 +/** Use IPv4 broadcast address for datagram delivery */ +#define VC_CONTAINER_NET_OPEN_FLAG_IP4_BROADCAST 8 +/* @} */ + +/** Mask of bits used in forcing address type */ +#define VC_CONTAINER_NET_OPEN_FLAG_FORCE_MASK 6 + +/** Blocks until data is available, or an error occurs. + * Used with the VC_CONTAINER_NET_CONTROL_SET_READ_TIMEOUT_MS control operation. */ +#define INFINITE_TIMEOUT_MS 0xFFFFFFFFUL + + +/** Opens a network socket instance. + * The network address can be a host name, dotted IP4, hex IP6 address or NULL. Passing NULL + * signifies the socket is either to be used as a datagram receiver or a stream server, + * depending on the flags. + * \ref VC_CONTAINER_NET_OPEN_FLAG_STREAM will open the socket for connected streaming. The default + * is to use connectionless datagrams. + * \ref VC_CONTAINER_NET_OPEN_FLAG_FORCE_IP4 will force the use of IPv4 addressing or fail to open + * the socket. The default is to pick the first available. + * \ref VC_CONTAINER_NET_OPEN_FLAG_FORCE_IP6 will force the use of IPv6 addressing or fail to open + * the socket. The default is to pick the first available. + * \ref VC_CONTAINER_NET_OPEN_FLAG_IP4_BROADCAST will use IPv4 broadcast addressing for a datagram + * sender. Use with an IPv6 address, stream socket or datagram receiver will raise an error. + * If the p_status parameter is not NULL, the status code will be written to it to indicate the + * reason for failure, or VC_CONTAINER_NET_SUCCESS on success. + * Sockets shall be bound and connected as necessary. Stream server sockets shall further need + * to have vc_container_net_listen and vc_container_net_accept called on them before data can be transferred. + * + * \param address Network address or NULL. + * \param port Network port or well-known name. This is the local port for receivers/servers. + * \param flags Flags controlling socket type. + * \param p_status Optional pointer to variable to receive status of operation. + * \return The socket instance or NULL on error. */ +VC_CONTAINER_NET_T *vc_container_net_open( const char *address, const char *port, + vc_container_net_open_flags_t flags, vc_container_net_status_t *p_status ); + +/** Closes a network socket instance. + * The p_ctx pointer must not be used after it has been closed. + * + * \param p_ctx The socket instance to close. + * \return The status code for closing the socket. */ +vc_container_net_status_t vc_container_net_close( VC_CONTAINER_NET_T *p_ctx ); + +/** Query the latest status of the socket. + * + * \param p_ctx The socket instance. + * \return The status of the socket. */ +vc_container_net_status_t vc_container_net_status( VC_CONTAINER_NET_T *p_ctx ); + +/** Read data from the socket. + * The function will read up to the requested number of bytes into the buffer. + * If there is no data immediately available to read, the function will block + * until data arrives, an error occurs or the timeout is reached (if set). + * When the function returns zero, the socket may have been closed, an error + * may have occurred, a zero length datagram received, or the timeout reached. + * Check vc_container_net_status() to differentiate. + * Attempting to read on a datagram sender socket will trigger an error. + * + * \param p_ctx The socket instance. + * \param buffer The buffer into which bytes will be read. + * \param size The maximum number of bytes to read. + * \return The number of bytes actually read. */ +size_t vc_container_net_read( VC_CONTAINER_NET_T *p_ctx, void *buffer, size_t size ); + +/** Write data to the socket. + * If the socket cannot send the requested number of bytes in one go, the function + * will return a value smaller than size. + * Attempting to write on a datagram receiver socket will trigger an error. + * + * \param p_ctx The socket instance. + * \param buffer The buffer from which bytes will be written. + * \param size The maximum number of bytes to write. + * \return The number of bytes actually written. */ +size_t vc_container_net_write( VC_CONTAINER_NET_T *p_ctx, const void *buffer, size_t size ); + +/** Start a stream server socket listening for connections from clients. + * Attempting to use this on anything other than a stream server socket shall + * trigger an error. + * + * \param p_ctx The socket instance. + * \param maximum_connections The maximum number of queued connections to allow. + * \return The status of the socket. */ +vc_container_net_status_t vc_container_net_listen( VC_CONTAINER_NET_T *p_ctx, uint32_t maximum_connections ); + +/** Accept a client connection on a listening stream server socket. + * Attempting to use this on anything other than a listening stream server socket + * shall trigger an error. + * When a client connection is made, the new instance representing it is returned + * via pp_client_ctx. + * + * \param p_server ctx The server socket instance. + * \param pp_client_ctx The address where the pointer to the new client's socket + * instance is written. + * \return The status of the socket. */ +vc_container_net_status_t vc_container_net_accept( VC_CONTAINER_NET_T *p_server_ctx, VC_CONTAINER_NET_T **pp_client_ctx ); + +/** Non-blocking check for data being available to read. + * If an error occurs, the function will return false and the error can be + * obtained using socket_status(). + * + * \param p_ctx The socket instance. + * \return True if there is data available to read immediately. */ +bool vc_container_net_is_data_available( VC_CONTAINER_NET_T *p_ctx ); + +/** Returns the maximum size of a datagram in bytes, for sending or receiving. + * The limit for reading from or writing to stream sockets will generally be + * greater than this value, although the call can also be made on such sockets. + * + * \param p_ctx The socket instance. + * \return The maximum size of a datagram in bytes. */ +size_t vc_container_net_maximum_datagram_size( VC_CONTAINER_NET_T *p_ctx ); + +/** Get the DNS name or IP address of a stream server client, if connected. + * The length of the name will be limited by name_len, taking into account a + * terminating NUL character. + * Calling this function on a non-stream server instance, or one that is not + * connected to a client, will result in an error status. + * + * \param p_ctx The socket instance. + * \param name Pointer where the name should be written. + * \param name_len Maximum number of characters to write to name. + * \return The status of the socket. */ +vc_container_net_status_t vc_container_net_get_client_name( VC_CONTAINER_NET_T *p_ctx, char *name, size_t name_len ); + +/** Get the port of a stream server client, if connected. + * The port is written to the address in host order. + * Calling this function on a non-stream server instance, or one that is not + * connected to a client, will result in an error status. + * + * \param p_ctx The socket instance. + * \param port Pointer where the port should be written. + * \return The status of the socket. */ +vc_container_net_status_t vc_container_net_get_client_port( VC_CONTAINER_NET_T *p_ctx , unsigned short *port ); + +/** Perform a control operation on the socket. + * See vc_container_net_control_t for more details. + * + * \param p_ctx The socket instance. + * \param operation The control operation to perform. + * \param args Variable list of additional arguments to the operation. + * \return The status of the socket. */ +vc_container_net_status_t vc_container_net_control( VC_CONTAINER_NET_T *p_ctx, vc_container_net_control_t operation, va_list args); + +/** Convert a 32-bit unsigned value from network order (big endian) to host order. + * + * \param value The value to be converted. + * \return The converted value. */ +uint32_t vc_container_net_to_host( uint32_t value ); + +/** Convert a 32-bit unsigned value from host order to network order (big endian). + * + * \param value The value to be converted. + * \return The converted value. */ +uint32_t vc_container_net_from_host( uint32_t value ); + +/** Convert a 16-bit unsigned value from network order (big endian) to host order. + * + * \param value The value to be converted. + * \return The converted value. */ +uint16_t vc_container_net_to_host_16( uint16_t value ); + +/** Convert a 16-bit unsigned value from host order to network order (big endian). + * + * \param value The value to be converted. + * \return The converted value. */ +uint16_t vc_container_net_from_host_16( uint16_t value ); + +#endif /* VC_NET_SOCKETS_H */ diff --git a/containers/net/net_sockets_bsd.c b/containers/net/net_sockets_bsd.c new file mode 100644 index 0000000..4503a9a --- /dev/null +++ b/containers/net/net_sockets_bsd.c @@ -0,0 +1,117 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "net_sockets.h" +#include "net_sockets_priv.h" +#include "containers/core/containers_common.h" + +/*****************************************************************************/ + +/** Default maximum datagram size. + * This is based on the default Ethernet MTU size, less the IP and UDP headers. + */ +#define DEFAULT_MAXIMUM_DATAGRAM_SIZE (1500 - 20 - 8) + +/** Maximum socket buffer size to use. */ +#define MAXIMUM_BUFFER_SIZE 65536 + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_private_last_error() +{ + switch (errno) + { + case EACCES: return VC_CONTAINER_NET_ERROR_ACCESS_DENIED; + case EFAULT: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case EINVAL: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case EMFILE: return VC_CONTAINER_NET_ERROR_TOO_BIG; + case EWOULDBLOCK: return VC_CONTAINER_NET_ERROR_WOULD_BLOCK; + case EINPROGRESS: return VC_CONTAINER_NET_ERROR_IN_PROGRESS; + case EALREADY: return VC_CONTAINER_NET_ERROR_IN_PROGRESS; + case EADDRINUSE: return VC_CONTAINER_NET_ERROR_IN_USE; + case EADDRNOTAVAIL: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case ENETDOWN: return VC_CONTAINER_NET_ERROR_NETWORK; + case ENETUNREACH: return VC_CONTAINER_NET_ERROR_NETWORK; + case ENETRESET: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case ECONNABORTED: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case ECONNRESET: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case ENOBUFS: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case ENOTCONN: return VC_CONTAINER_NET_ERROR_NOT_CONNECTED; + case ESHUTDOWN: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case ETIMEDOUT: return VC_CONTAINER_NET_ERROR_TIMED_OUT; + case ECONNREFUSED: return VC_CONTAINER_NET_ERROR_CONNECTION_REFUSED; + case ELOOP: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case ENAMETOOLONG: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case EHOSTDOWN: return VC_CONTAINER_NET_ERROR_NETWORK; + case EHOSTUNREACH: return VC_CONTAINER_NET_ERROR_NETWORK; + case EUSERS: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case EDQUOT: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case ESTALE: return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + + /* All other errors are unexpected, so just map to a general purpose error code. */ + default: + return VC_CONTAINER_NET_ERROR_GENERAL; + } +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_private_init() +{ + /* No additional initialization required */ + return VC_CONTAINER_NET_SUCCESS; +} + +/*****************************************************************************/ +void vc_container_net_private_deinit() +{ + /* No additional deinitialization required */ +} + +/*****************************************************************************/ +void vc_container_net_private_close( SOCKET_T sock ) +{ + close(sock); +} + +/*****************************************************************************/ +void vc_container_net_private_set_reusable( SOCKET_T sock, bool enable ) +{ + int opt = enable ? 1 : 0; + + (void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); +} + +/*****************************************************************************/ +size_t vc_container_net_private_maximum_datagram_size( SOCKET_T sock ) +{ + (void)sock; + + /* No easy way to determine this, just use the default. */ + return DEFAULT_MAXIMUM_DATAGRAM_SIZE; +} diff --git a/containers/net/net_sockets_bsd.h b/containers/net/net_sockets_bsd.h new file mode 100644 index 0000000..fd554c9 --- /dev/null +++ b/containers/net/net_sockets_bsd.h @@ -0,0 +1,43 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _NET_SOCKETS_BSD_H_ +#define _NET_SOCKETS_BSD_H_ + +#include +#include +#include +#include +#include + +typedef int SOCKET_T; +typedef socklen_t SOCKADDR_LEN_T; +typedef void *SOCKOPT_CAST_T; +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 + +#endif /* _NET_SOCKETS_BSD_H_ */ diff --git a/containers/net/net_sockets_common.c b/containers/net/net_sockets_common.c new file mode 100644 index 0000000..b3815f3 --- /dev/null +++ b/containers/net/net_sockets_common.c @@ -0,0 +1,619 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "net_sockets.h" +#include "net_sockets_priv.h" + +/*****************************************************************************/ + +struct vc_container_net_tag +{ + /** The underlying socket */ + SOCKET_T socket; + /** Last error raised on the socket instance. */ + vc_container_net_status_t status; + /** Simple socket type */ + vc_container_net_type_t type; + /** Socket address, used for sending datagrams. */ + union { + struct sockaddr_storage storage; + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + } to_addr; + /** Number of bytes in to_addr that have been filled. */ + SOCKADDR_LEN_T to_addr_len; + /** Maximum size of datagrams. */ + size_t max_datagram_size; + /** Timeout to use when reading from a socket. INFINITE_TIMEOUT_MS waits forever. */ + uint32_t read_timeout_ms; +}; + +/*****************************************************************************/ +static void socket_clear_address(struct sockaddr *p_addr) +{ + switch (p_addr->sa_family) + { + case AF_INET: + { + struct sockaddr_in *p_addr_v4 = (struct sockaddr_in *)p_addr; + + memset(&p_addr_v4->sin_addr, 0, sizeof(p_addr_v4->sin_addr)); + } + break; + case AF_INET6: + { + struct sockaddr_in6 *p_addr_v6 = (struct sockaddr_in6 *)p_addr; + + memset(&p_addr_v6->sin6_addr, 0, sizeof(p_addr_v6->sin6_addr)); + } + break; + default: + /* Invalid or unsupported address family */ + vc_container_assert(0); + } +} + +/*****************************************************************************/ +static vc_container_net_status_t socket_set_read_buffer_size(VC_CONTAINER_NET_T *p_ctx, + uint32_t buffer_size) +{ + int result; + const SOCKOPT_CAST_T optptr = (const SOCKOPT_CAST_T)&buffer_size; + + result = setsockopt(p_ctx->socket, SOL_SOCKET, SO_RCVBUF, optptr, sizeof(buffer_size)); + + if (result == SOCKET_ERROR) + return vc_container_net_private_last_error(); + + return VC_CONTAINER_NET_SUCCESS; +} + +/*****************************************************************************/ +static vc_container_net_status_t socket_set_read_timeout_ms(VC_CONTAINER_NET_T *p_ctx, + uint32_t timeout_ms) +{ + p_ctx->read_timeout_ms = timeout_ms; + return VC_CONTAINER_NET_SUCCESS; +} + +/*****************************************************************************/ +static bool socket_wait_for_data( VC_CONTAINER_NET_T *p_ctx, uint32_t timeout_ms ) +{ + int result; + fd_set set; + struct timeval tv; + + if (timeout_ms == INFINITE_TIMEOUT_MS) + return true; + + FD_ZERO(&set); + FD_SET(p_ctx->socket, &set); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms - tv.tv_sec * 1000) * 1000; + result = select(p_ctx->socket + 1, &set, NULL, NULL, &tv); + + if (result == SOCKET_ERROR) + p_ctx->status = vc_container_net_private_last_error(); + else + p_ctx->status = VC_CONTAINER_NET_SUCCESS; + + return (result == 1); +} + +/*****************************************************************************/ +VC_CONTAINER_NET_T *vc_container_net_open( const char *address, const char *port, + vc_container_net_open_flags_t flags, vc_container_net_status_t *p_status ) +{ + VC_CONTAINER_NET_T *p_ctx; + struct addrinfo hints, *info, *p; + int result; + vc_container_net_status_t status; + SOCKET_T sock = INVALID_SOCKET; + + status = vc_container_net_private_init(); + if (status != VC_CONTAINER_NET_SUCCESS) + { + LOG_ERROR(NULL, "vc_container_net_open: platform initialization failure: %d", status); + if (p_status) + *p_status = status; + return NULL; + } + + p_ctx = (VC_CONTAINER_NET_T *)malloc(sizeof(VC_CONTAINER_NET_T)); + if (!p_ctx) + { + if (p_status) + *p_status = VC_CONTAINER_NET_ERROR_NO_MEMORY; + + LOG_ERROR(NULL, "vc_container_net_open: malloc fail for VC_CONTAINER_NET_T"); + vc_container_net_private_deinit(); + return NULL; + } + + /* Initialize the net socket instance structure */ + memset(p_ctx, 0, sizeof(*p_ctx)); + p_ctx->socket = INVALID_SOCKET; + if (flags & VC_CONTAINER_NET_OPEN_FLAG_STREAM) + p_ctx->type = address ? STREAM_CLIENT : STREAM_SERVER; + else + p_ctx->type = address ? DATAGRAM_SENDER : DATAGRAM_RECEIVER; + + /* Create the address info linked list from the data provided */ + memset(&hints, 0, sizeof(hints)); + switch (flags & VC_CONTAINER_NET_OPEN_FLAG_FORCE_MASK) + { + case 0: + hints.ai_family = AF_UNSPEC; + break; + case VC_CONTAINER_NET_OPEN_FLAG_FORCE_IP4: + hints.ai_family = AF_INET; + break; + case VC_CONTAINER_NET_OPEN_FLAG_FORCE_IP6: + hints.ai_family = AF_INET6; + break; + default: + status = VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + LOG_ERROR(NULL, "vc_container_net_open: invalid address forcing flag"); + goto error; + } + hints.ai_socktype = (flags & VC_CONTAINER_NET_OPEN_FLAG_STREAM) ? SOCK_STREAM : SOCK_DGRAM; + + result = getaddrinfo(address, port, &hints, &info); + if (result) + { + status = vc_container_net_private_last_error(); + LOG_ERROR(NULL, "vc_container_net_open: unable to get address info: %d", status); + goto error; + } + + /* Not all address infos may be useable. Search for one that is by skipping any + * that provoke errors. */ + for(p = info; (p != NULL) && (sock == INVALID_SOCKET) ; p = p->ai_next) + { + sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sock == INVALID_SOCKET) + { + status = vc_container_net_private_last_error(); + continue; + } + + switch (p_ctx->type) + { + case STREAM_CLIENT: + /* Simply connect to the given address/port */ + if (connect(sock, p->ai_addr, p->ai_addrlen) == SOCKET_ERROR) + status = vc_container_net_private_last_error(); + break; + + case DATAGRAM_SENDER: + /* Nothing further to do */ + break; + + case STREAM_SERVER: + /* Try to avoid socket reuse timing issues on TCP server sockets */ + vc_container_net_private_set_reusable(sock, true); + + /* Allow any source address */ + socket_clear_address(p->ai_addr); + + if (bind(sock, p->ai_addr, p->ai_addrlen) == SOCKET_ERROR) + status = vc_container_net_private_last_error(); + break; + + case DATAGRAM_RECEIVER: + /* Allow any source address */ + socket_clear_address(p->ai_addr); + + if (bind(sock, p->ai_addr, p->ai_addrlen) == SOCKET_ERROR) + status = vc_container_net_private_last_error(); + break; + } + + if (status == VC_CONTAINER_NET_SUCCESS) + { + /* Save addressing information for later use */ + p_ctx->to_addr_len = p->ai_addrlen; + memcpy(&p_ctx->to_addr, p->ai_addr, p->ai_addrlen); + } else { + vc_container_net_private_close(sock); /* Try next entry in list */ + sock = INVALID_SOCKET; + } + } + + freeaddrinfo(info); + + if (sock == INVALID_SOCKET) + { + LOG_ERROR(NULL, "vc_container_net_open: failed to open socket: %d", status); + goto error; + } + + p_ctx->socket = sock; + p_ctx->max_datagram_size = vc_container_net_private_maximum_datagram_size(sock); + p_ctx->read_timeout_ms = INFINITE_TIMEOUT_MS; + + if (p_status) + *p_status = VC_CONTAINER_NET_SUCCESS; + + return p_ctx; + +error: + if (p_status) + *p_status = status; + (void)vc_container_net_close(p_ctx); + return NULL; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_close( VC_CONTAINER_NET_T *p_ctx ) +{ + if (!p_ctx) + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + + if (p_ctx->socket != INVALID_SOCKET) + { + vc_container_net_private_close(p_ctx->socket); + p_ctx->socket = INVALID_SOCKET; + } + free(p_ctx); + + vc_container_net_private_deinit(); + + return VC_CONTAINER_NET_SUCCESS; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_status( VC_CONTAINER_NET_T *p_ctx ) +{ + if (!p_ctx) + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + return p_ctx->status; +} + +/*****************************************************************************/ +size_t vc_container_net_read( VC_CONTAINER_NET_T *p_ctx, void *buffer, size_t size ) +{ + int result = 0; + + if (!p_ctx) + return 0; + + if (!buffer) + { + p_ctx->status = VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + return 0; + } + + p_ctx->status = VC_CONTAINER_NET_SUCCESS; + + switch (p_ctx->type) + { + case STREAM_CLIENT: + case STREAM_SERVER: + /* Receive data from the stream */ + if (socket_wait_for_data(p_ctx, p_ctx->read_timeout_ms)) + { + result = recv(p_ctx->socket, buffer, (int)size, 0); + if (!result) + p_ctx->status = VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + } else + p_ctx->status = VC_CONTAINER_NET_ERROR_TIMED_OUT; + break; + + case DATAGRAM_RECEIVER: + { + /* Receive the packet */ + /* FIXME Potential for data loss, as rest of packet will be lost if buffer was not large enough */ + if (socket_wait_for_data(p_ctx, p_ctx->read_timeout_ms)) + { + result = recvfrom(p_ctx->socket, buffer, size, 0, &p_ctx->to_addr.sa, &p_ctx->to_addr_len); + if (!result) + p_ctx->status = VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + } else + p_ctx->status = VC_CONTAINER_NET_ERROR_TIMED_OUT; + } + break; + + default: /* DATAGRAM_SENDER */ + p_ctx->status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + result = 0; + break; + } + + if (result == SOCKET_ERROR) + { + p_ctx->status = vc_container_net_private_last_error(); + result = 0; + } + + return (size_t)result; +} + +/*****************************************************************************/ +size_t vc_container_net_write( VC_CONTAINER_NET_T *p_ctx, const void *buffer, size_t size ) +{ + int result; + + if (!p_ctx) + return 0; + + if (!buffer) + { + p_ctx->status = VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + return 0; + } + + p_ctx->status = VC_CONTAINER_NET_SUCCESS; + + switch (p_ctx->type) + { + case STREAM_CLIENT: + case STREAM_SERVER: + /* Send data to the stream */ + result = send(p_ctx->socket, buffer, (int)size, 0); + break; + + case DATAGRAM_SENDER: + /* Send the datagram */ + + if (size > p_ctx->max_datagram_size) + size = p_ctx->max_datagram_size; + + result = sendto(p_ctx->socket, buffer, size, 0, &p_ctx->to_addr.sa, p_ctx->to_addr_len); + break; + + default: /* DATAGRAM_RECEIVER */ + p_ctx->status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + result = 0; + break; + } + + if (result == SOCKET_ERROR) + { + p_ctx->status = vc_container_net_private_last_error(); + result = 0; + } + + return (size_t)result; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_listen( VC_CONTAINER_NET_T *p_ctx, uint32_t maximum_connections ) +{ + if (!p_ctx) + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + + p_ctx->status = VC_CONTAINER_NET_SUCCESS; + + if (p_ctx->type == STREAM_SERVER) + { + if (listen(p_ctx->socket, maximum_connections) == SOCKET_ERROR) + p_ctx->status = vc_container_net_private_last_error(); + } else { + p_ctx->status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + } + + return p_ctx->status; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_accept( VC_CONTAINER_NET_T *p_server_ctx, VC_CONTAINER_NET_T **pp_client_ctx ) +{ + VC_CONTAINER_NET_T *p_client_ctx = NULL; + + if (!p_server_ctx) + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + + if (!pp_client_ctx) + return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + + *pp_client_ctx = NULL; + + if (p_server_ctx->type != STREAM_SERVER) + { + p_server_ctx->status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + goto error; + } + + p_client_ctx = (VC_CONTAINER_NET_T *)malloc(sizeof(VC_CONTAINER_NET_T)); + if (!p_client_ctx) + { + p_server_ctx->status = VC_CONTAINER_NET_ERROR_NO_MEMORY; + goto error; + } + + /* Initialise the new context with the address information from the server context */ + memset(p_client_ctx, 0, sizeof(*p_client_ctx)); + memcpy(&p_client_ctx->to_addr, &p_server_ctx->to_addr, p_server_ctx->to_addr_len); + p_client_ctx->to_addr_len = p_server_ctx->to_addr_len; + + p_client_ctx->socket = accept(p_server_ctx->socket, &p_client_ctx->to_addr.sa, &p_client_ctx->to_addr_len); + + if (p_client_ctx->socket == INVALID_SOCKET) + { + p_server_ctx->status = vc_container_net_private_last_error(); + goto error; + } + + /* Need to bump up the initialisation count, as a new context has been created */ + p_server_ctx->status = vc_container_net_private_init(); + if (p_server_ctx->status != VC_CONTAINER_NET_SUCCESS) + goto error; + + p_client_ctx->type = STREAM_CLIENT; + p_client_ctx->max_datagram_size = vc_container_net_private_maximum_datagram_size(p_client_ctx->socket); + p_client_ctx->read_timeout_ms = INFINITE_TIMEOUT_MS; + p_client_ctx->status = VC_CONTAINER_NET_SUCCESS; + + *pp_client_ctx = p_client_ctx; + return VC_CONTAINER_NET_SUCCESS; + +error: + if (p_client_ctx) + free(p_client_ctx); + return p_server_ctx->status; +} + +/*****************************************************************************/ +bool vc_container_net_is_data_available( VC_CONTAINER_NET_T *p_ctx ) +{ + if (!p_ctx) + return false; + + if (p_ctx->type == DATAGRAM_SENDER) + { + p_ctx->status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + return false; + } + + return socket_wait_for_data(p_ctx, 0); +} + +/*****************************************************************************/ +size_t vc_container_net_maximum_datagram_size( VC_CONTAINER_NET_T *p_ctx ) +{ + return p_ctx ? p_ctx->max_datagram_size : 0; +} + +/*****************************************************************************/ +static vc_container_net_status_t translate_getnameinfo_error( int error ) +{ + switch (error) + { + case EAI_AGAIN: return VC_CONTAINER_NET_ERROR_TRY_AGAIN; + case EAI_FAIL: return VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND; + case EAI_MEMORY: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case EAI_NONAME: return VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND; + + /* All other errors are unexpected, so just map to a general purpose error code. */ + default: + return VC_CONTAINER_NET_ERROR_GENERAL; + } +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_get_client_name( VC_CONTAINER_NET_T *p_ctx, char *name, size_t name_len ) +{ + int result; + + if (!p_ctx) + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + + if (p_ctx->socket == INVALID_SOCKET) + p_ctx->status = VC_CONTAINER_NET_ERROR_NOT_CONNECTED; + else if (!name || !name_len) + p_ctx->status = VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + else if ((result = getnameinfo(&p_ctx->to_addr.sa, p_ctx->to_addr_len, name, name_len, NULL, 0, 0)) != 0) + p_ctx->status = translate_getnameinfo_error(result); + else + p_ctx->status = VC_CONTAINER_NET_SUCCESS; + + return p_ctx->status; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_get_client_port( VC_CONTAINER_NET_T *p_ctx , unsigned short *port ) +{ + if (!p_ctx) + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + + if (p_ctx->socket == INVALID_SOCKET) + p_ctx->status = VC_CONTAINER_NET_ERROR_NOT_CONNECTED; + else if (!port) + p_ctx->status = VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + else + { + p_ctx->status = VC_CONTAINER_NET_SUCCESS; + switch (p_ctx->to_addr.sa.sa_family) + { + case AF_INET: + *port = ntohs(p_ctx->to_addr.in.sin_port); + break; + case AF_INET6: + *port = ntohs(p_ctx->to_addr.in6.sin6_port); + break; + default: + /* Highly unexepcted address family! */ + p_ctx->status = VC_CONTAINER_NET_ERROR_GENERAL; + } + } + + return p_ctx->status; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_control( VC_CONTAINER_NET_T *p_ctx, + vc_container_net_control_t operation, + va_list args) +{ + vc_container_net_status_t status; + + switch (operation) + { + case VC_CONTAINER_NET_CONTROL_SET_READ_BUFFER_SIZE: + status = socket_set_read_buffer_size(p_ctx, va_arg(args, uint32_t)); + break; + case VC_CONTAINER_NET_CONTROL_SET_READ_TIMEOUT_MS: + status = socket_set_read_timeout_ms(p_ctx, va_arg(args, uint32_t)); + break; + default: + status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + } + + return status; +} + +/*****************************************************************************/ +uint32_t vc_container_net_to_host( uint32_t value ) +{ + return ntohl(value); +} + +/*****************************************************************************/ +uint32_t vc_container_net_from_host( uint32_t value ) +{ + return htonl(value); +} + +/*****************************************************************************/ +uint16_t vc_container_net_to_host_16( uint16_t value ) +{ + return ntohs(value); +} + +/*****************************************************************************/ +uint16_t vc_container_net_from_host_16( uint16_t value ) +{ + return htons(value); +} diff --git a/containers/net/net_sockets_null.c b/containers/net/net_sockets_null.c new file mode 100644 index 0000000..a8fe8fb --- /dev/null +++ b/containers/net/net_sockets_null.c @@ -0,0 +1,175 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "net_sockets.h" +#include "containers/core/containers_common.h" + +/*****************************************************************************/ + +struct vc_container_net_tag +{ + uint32_t dummy; /* C requires structs not to be empty. */ +}; + +/*****************************************************************************/ +VC_CONTAINER_NET_T *vc_container_net_open( const char *address, const char *port, + vc_container_net_open_flags_t flags, vc_container_net_status_t *p_status ) +{ + VC_CONTAINER_PARAM_UNUSED(address); + VC_CONTAINER_PARAM_UNUSED(port); + VC_CONTAINER_PARAM_UNUSED(flags); + + if (p_status) + *p_status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED; + + return NULL; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_close( VC_CONTAINER_NET_T *p_ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_status( VC_CONTAINER_NET_T *p_ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; +} + +/*****************************************************************************/ +size_t vc_container_net_read( VC_CONTAINER_NET_T *p_ctx, void *buffer, size_t size ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(buffer); + VC_CONTAINER_PARAM_UNUSED(size); + + return 0; +} + +/*****************************************************************************/ +size_t vc_container_net_write( VC_CONTAINER_NET_T *p_ctx, const void *buffer, size_t size ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(buffer); + VC_CONTAINER_PARAM_UNUSED(size); + + return 0; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_listen( VC_CONTAINER_NET_T *p_ctx, uint32_t maximum_connections ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(maximum_connections); + + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_accept( VC_CONTAINER_NET_T *p_server_ctx, VC_CONTAINER_NET_T **pp_client_ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(p_server_ctx); + VC_CONTAINER_PARAM_UNUSED(pp_client_ctx); + + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; +} + +/*****************************************************************************/ +bool vc_container_net_is_data_available( VC_CONTAINER_NET_T *p_ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + return false; +} + +/*****************************************************************************/ +size_t vc_container_net_maximum_datagram_size( VC_CONTAINER_NET_T *p_ctx ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + return 0; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_get_client_name( VC_CONTAINER_NET_T *p_ctx, char *name, size_t name_len ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(name); + VC_CONTAINER_PARAM_UNUSED(name_len); + + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_get_client_port( VC_CONTAINER_NET_T *p_ctx , unsigned short *port ) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(port); + + return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_control( VC_CONTAINER_NET_T *p_ctx, + vc_container_net_control_t operation, + va_list args) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(operation); + VC_CONTAINER_PARAM_UNUSED(args); + + return VC_CONTAINER_NET_ERROR_NOT_ALLOWED; +} + +/*****************************************************************************/ +uint32_t vc_container_net_to_host( uint32_t value ) +{ + return value; +} + +/*****************************************************************************/ +uint32_t vc_container_net_from_host( uint32_t value ) +{ + return value; +} + +/*****************************************************************************/ +uint16_t vc_container_net_to_host_16( uint16_t value ) +{ + return value; +} + +/*****************************************************************************/ +uint16_t vc_container_net_from_host_16( uint16_t value ) +{ + return value; +} diff --git a/containers/net/net_sockets_priv.h b/containers/net/net_sockets_priv.h new file mode 100644 index 0000000..4186083 --- /dev/null +++ b/containers/net/net_sockets_priv.h @@ -0,0 +1,85 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _NET_SOCKETS_PRIV_H_ +#define _NET_SOCKETS_PRIV_H_ + +#include "net_sockets.h" + +#ifdef WIN32 +#include "net_sockets_win32.h" +#else +#include "net_sockets_bsd.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + STREAM_CLIENT = 0, /**< TCP client */ + STREAM_SERVER, /**< TCP server */ + DATAGRAM_SENDER, /**< UDP sender */ + DATAGRAM_RECEIVER /**< UDP receiver */ +} vc_container_net_type_t; + + +/** Perform implementation-specific per-socket initialization. + * + * \return VC_CONTAINER_NET_SUCCESS or one of the error codes on failure. */ +vc_container_net_status_t vc_container_net_private_init( void ); + +/** Perform implementation-specific per-socket deinitialization. + * This function is always called once for each successful call to socket_private_init(). */ +void vc_container_net_private_deinit( void ); + +/** Return the last error from the socket implementation. */ +vc_container_net_status_t vc_container_net_private_last_error( void ); + +/** Implementation-specific internal socket close. + * + * \param sock Internal socket to be closed. */ +void vc_container_net_private_close( SOCKET_T sock ); + +/** Enable or disable socket address reusability. + * + * \param sock Internal socket to be closed. + * \param enable True to enable reusability, false to clear it. */ +void vc_container_net_private_set_reusable( SOCKET_T sock, bool enable ); + +/** Query the maximum datagram size for the socket. + * + * \param sock The socket to query. + * \return The maximum supported datagram size on the socket. */ +size_t vc_container_net_private_maximum_datagram_size( SOCKET_T sock ); + +#ifdef __cplusplus +} +#endif + +#endif /* _NET_SOCKETS_PRIV_H_ */ diff --git a/containers/net/net_sockets_win32.c b/containers/net/net_sockets_win32.c new file mode 100644 index 0000000..e0dd374 --- /dev/null +++ b/containers/net/net_sockets_win32.c @@ -0,0 +1,140 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "net_sockets.h" +#include "net_sockets_priv.h" +#include "containers/core/containers_common.h" + +#pragma comment(lib, "Ws2_32.lib") + +/*****************************************************************************/ + +/** Default maximum datagram size. + * This is based on the default Ethernet MTU size, less the IP and UDP headers. + */ +#define DEFAULT_MAXIMUM_DATAGRAM_SIZE (1500 - 20 - 8) + +/** Maximum socket buffer size to use. */ +#define MAXIMUM_BUFFER_SIZE 65536 + +/*****************************************************************************/ +static vc_container_net_status_t translate_error_status( int error ) +{ + switch (error) + { + case WSA_INVALID_HANDLE: return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + case WSA_NOT_ENOUGH_MEMORY: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case WSA_INVALID_PARAMETER: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case WSAEACCES: return VC_CONTAINER_NET_ERROR_ACCESS_DENIED; + case WSAEFAULT: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case WSAEINVAL: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case WSAEMFILE: return VC_CONTAINER_NET_ERROR_TOO_BIG; + case WSAEWOULDBLOCK: return VC_CONTAINER_NET_ERROR_WOULD_BLOCK; + case WSAEINPROGRESS: return VC_CONTAINER_NET_ERROR_IN_PROGRESS; + case WSAEALREADY: return VC_CONTAINER_NET_ERROR_IN_PROGRESS; + case WSAEADDRINUSE: return VC_CONTAINER_NET_ERROR_IN_USE; + case WSAEADDRNOTAVAIL: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case WSAENETDOWN: return VC_CONTAINER_NET_ERROR_NETWORK; + case WSAENETUNREACH: return VC_CONTAINER_NET_ERROR_NETWORK; + case WSAENETRESET: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case WSAECONNABORTED: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case WSAECONNRESET: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case WSAENOBUFS: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case WSAENOTCONN: return VC_CONTAINER_NET_ERROR_NOT_CONNECTED; + case WSAESHUTDOWN: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case WSAETIMEDOUT: return VC_CONTAINER_NET_ERROR_TIMED_OUT; + case WSAECONNREFUSED: return VC_CONTAINER_NET_ERROR_CONNECTION_REFUSED; + case WSAELOOP: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case WSAENAMETOOLONG: return VC_CONTAINER_NET_ERROR_INVALID_PARAMETER; + case WSAEHOSTDOWN: return VC_CONTAINER_NET_ERROR_NETWORK; + case WSAEHOSTUNREACH: return VC_CONTAINER_NET_ERROR_NETWORK; + case WSAEPROCLIM: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case WSAEUSERS: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case WSAEDQUOT: return VC_CONTAINER_NET_ERROR_NO_MEMORY; + case WSAESTALE: return VC_CONTAINER_NET_ERROR_INVALID_SOCKET; + case WSAEDISCON: return VC_CONTAINER_NET_ERROR_CONNECTION_LOST; + case WSAHOST_NOT_FOUND: return VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND; + case WSATRY_AGAIN: return VC_CONTAINER_NET_ERROR_TRY_AGAIN; + case WSANO_RECOVERY: return VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND; + case WSANO_DATA: return VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND; + + /* All other errors are unexpected, so just map to a general purpose error code. */ + default: + return VC_CONTAINER_NET_ERROR_GENERAL; + } +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_private_last_error() +{ + return translate_error_status( WSAGetLastError() ); +} + +/*****************************************************************************/ +vc_container_net_status_t vc_container_net_private_init() +{ + WSADATA wsa_data; + int result; + + result = WSAStartup(MAKEWORD(2,2), &wsa_data); + if (result) + return translate_error_status( result ); + + return VC_CONTAINER_NET_SUCCESS; +} + +/*****************************************************************************/ +void vc_container_net_private_deinit() +{ + WSACleanup(); +} + +/*****************************************************************************/ +void vc_container_net_private_close( SOCKET_T sock ) +{ + closesocket(sock); +} + +/*****************************************************************************/ +void vc_container_net_private_set_reusable( SOCKET_T sock, bool enable ) +{ + BOOL opt = enable ? TRUE : FALSE; + + (void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); +} + +/*****************************************************************************/ +size_t vc_container_net_private_maximum_datagram_size( SOCKET_T sock ) +{ + size_t max_datagram_size = DEFAULT_MAXIMUM_DATAGRAM_SIZE; + int opt_size = sizeof(max_datagram_size); + + /* Ignore errors and use the default if necessary */ + (void)getsockopt(sock, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *)&max_datagram_size, &opt_size); + + return max_datagram_size; +} diff --git a/containers/net/net_sockets_win32.h b/containers/net/net_sockets_win32.h new file mode 100644 index 0000000..86edc3a --- /dev/null +++ b/containers/net/net_sockets_win32.h @@ -0,0 +1,38 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _NET_SOCKETS_WIN32_H_ +#define _NET_SOCKETS_WIN32_H_ + +#include +#include + +typedef SOCKET SOCKET_T; +typedef int SOCKADDR_LEN_T; +typedef char *SOCKOPT_CAST_T; + +#endif /* _NET_SOCKETS_WIN32_H_ */ diff --git a/containers/packetizers.h b/containers/packetizers.h new file mode 100644 index 0000000..7c6fc69 --- /dev/null +++ b/containers/packetizers.h @@ -0,0 +1,159 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef VC_PACKETIZERS_H +#define VC_PACKETIZERS_H + +/** \file packetizers.h + * Public API for packetizing data (i.e. framing and timestamping) + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "containers/containers.h" + +/** \defgroup VcPacketizerApi Packetizer API + * API for packetizers */ +/* @{ */ + +/** \name Packetizer flags + * \anchor packetizerflags + * The following flags describe properties of a packetizer */ +/* @{ */ +#define VC_PACKETIZER_FLAG_ES_CHANGED 0x1 /**< ES definition has changed */ +/* @} */ + +/** Definition of the packetizer type */ +typedef struct VC_PACKETIZER_T +{ + struct VC_PACKETIZER_PRIVATE_T *priv; /**< Private member used by the implementation */ + uint32_t flags; /**< Flags describing the properties of a packetizer. + * See \ref packetizerflags "Packetizer flags". */ + + VC_CONTAINER_ES_FORMAT_T *in; /**< Format of the input elementary stream */ + VC_CONTAINER_ES_FORMAT_T *out; /**< Format of the output elementary stream */ + + uint32_t max_frame_size; /**< Maximum size of a packetized frame */ + +} VC_PACKETIZER_T; + +/** Open a packetizer to convert the input format into the requested output format. + * This will create an an instance of a packetizer and its associated context. + * + * If no packetizer is found for the requested format, this will return a null pointer as well as + * an error code indicating why this failed. + * + * \param in Input elementary stream format + * \param out_variant Requested output variant for the output elementary stream format + * \param status Returns the status of the operation + * \return A pointer to the context of the new instance of the packetizer. + * Returns NULL on failure. + */ +VC_PACKETIZER_T *vc_packetizer_open(VC_CONTAINER_ES_FORMAT_T *in, VC_CONTAINER_FOURCC_T out_variant, + VC_CONTAINER_STATUS_T *status); + +/** Closes an instance of a packetizer. + * This will free all the resources associated with the context. + * + * \param context Pointer to the context of the instance to close + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_packetizer_close( VC_PACKETIZER_T *context ); + +/** \name Packetizer flags + * The following flags can be passed during a packetize call */ +/* @{ */ +/** Type definition for the packetizer flags */ +typedef uint32_t VC_PACKETIZER_FLAGS_T; +/** Ask the packetizer to only return information on the next packet without reading it */ +#define VC_PACKETIZER_FLAG_INFO 0x1 +/** Ask the packetizer to skip the next packet */ +#define VC_PACKETIZER_FLAG_SKIP 0x2 +/** Ask the packetizer to flush any data being processed */ +#define VC_PACKETIZER_FLAG_FLUSH 0x4 +/** Force the packetizer to release an input packet */ +#define VC_PACKETIZER_FLAG_FORCE_RELEASE_INPUT 0x8 +/* @} */ + +/** Push a new packet of data to the packetizer. + * This is the mechanism used to feed data into the packetizer. Once a packet has been + * pushed into the packetizer it is owned by the packetizer until released by a call to + * \ref vc_packetizer_pop + * + * \param context Pointer to the context of the packetizer to use + * \param packet Pointer to the VC_CONTAINER_PACKET_T structure describing the data packet + * to push into the packetizer. + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_packetizer_push( VC_PACKETIZER_T *context, + VC_CONTAINER_PACKET_T *packet); + +/** Pop a packet of data from the packetizer. + * This allows the client to retrieve consumed data from the packetizer. Packets returned by + * the packetizer in this manner can then be released / recycled by the client. + * It is possible for the client to retreive non-consumed data by passing the + * VC_PACKETIZER_FLAG_FORCE_RELEASE_INPUT flag. This will however trigger some internal buffering + * inside the packetizer and thus is less efficient. + * + * \param context Pointer to the context of the packetizer to use + * \param packet Pointer used to return a consumed packet + * \param flags Miscellaneous flags controlling the operation + * + * \return VC_CONTAINER_SUCCESS if a consumed packet was retreived, + * VC_CONTAINER_ERROR_INCOMPLETE_DATA if none is available. + */ +VC_CONTAINER_STATUS_T vc_packetizer_pop( VC_PACKETIZER_T *context, + VC_CONTAINER_PACKET_T **packet, VC_PACKETIZER_FLAGS_T flags); + +/** Read packetized data out of the packetizer. + * + * \param context Pointer to the context of the packetizer to use + * \param packet Pointer to the VC_CONTAINER_PACKET_T structure describing the data packet + * This might need to be partially filled before the call (buffer, buffer_size) + * depending on the flags used. + * \param flags Miscellaneous flags controlling the operation + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_packetizer_read( VC_PACKETIZER_T *context, + VC_CONTAINER_PACKET_T *out, VC_PACKETIZER_FLAGS_T flags); + +/** Reset packetizer state. + * This will reset the state of the packetizer as well as mark all data pushed to it as consumed. + * + * \param context Pointer to the context of the packetizer to reset + * \return the status of the operation + */ +VC_CONTAINER_STATUS_T vc_packetizer_reset( VC_PACKETIZER_T *context ); + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* VC_PACKETIZERS_H */ diff --git a/containers/pcm/pcm_packetizer.c b/containers/pcm/pcm_packetizer.c new file mode 100644 index 0000000..8dea398 --- /dev/null +++ b/containers/pcm/pcm_packetizer.c @@ -0,0 +1,269 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** \file + * Implementation of a PCM packetizer. + */ + +#include +#include + +#include "containers/packetizers.h" +#include "containers/core/packetizers_private.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_time.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_bytestream.h" + +#define FRAME_SIZE (16*1024) /**< Arbitrary value which is neither too small nor too big */ +#define FACTOR_SHIFT 4 /**< Shift applied to the conversion factor */ + +VC_CONTAINER_STATUS_T pcm_packetizer_open( VC_PACKETIZER_T * ); + +/*****************************************************************************/ +enum conversion { + CONVERSION_NONE = 0, + CONVERSION_U8_TO_S16L, + CONVERSION_UNKNOWN +}; + +typedef struct VC_PACKETIZER_MODULE_T { + enum { + STATE_NEW_PACKET = 0, + STATE_DATA + } state; + + unsigned int samples_per_frame; + unsigned int bytes_per_sample; + unsigned int max_frame_size; + + uint32_t bytes_read; + unsigned int frame_size; + + enum conversion conversion; + unsigned int conversion_factor; +} VC_PACKETIZER_MODULE_T; + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T pcm_packetizer_close( VC_PACKETIZER_T *p_ctx ) +{ + free(p_ctx->priv->module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T pcm_packetizer_reset( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + module->state = STATE_NEW_PACKET; + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static void convert_pcm_u8_to_s16l( uint8_t **p_out, uint8_t *in, size_t size) +{ + int16_t *out = (int16_t *)*p_out; + uint8_t tmp; + + while(size--) + { + tmp = *in++; + *out++ = ((tmp - 128) << 8) | tmp; + } + *p_out = (uint8_t *)out; +} + +/*****************************************************************************/ +static void convert_pcm( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_BYTESTREAM_T *stream, size_t size, uint8_t *out ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + uint8_t tmp[256]; + size_t tmp_size; + + while(size) + { + tmp_size = MIN(sizeof(tmp), size); + bytestream_get(stream, tmp, tmp_size); + if (module->conversion == CONVERSION_U8_TO_S16L) + convert_pcm_u8_to_s16l(&out, tmp, tmp_size); + else + bytestream_skip(stream, tmp_size); + size -= tmp_size; + } +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T pcm_packetizer_packetize( VC_PACKETIZER_T *p_ctx, + VC_CONTAINER_PACKET_T *out, VC_PACKETIZER_FLAGS_T flags ) +{ + VC_PACKETIZER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_BYTESTREAM_T *stream = &p_ctx->priv->stream; + VC_CONTAINER_TIME_T *time = &p_ctx->priv->time; + int64_t pts, dts; + size_t offset, size; + + while(1) switch (module->state) + { + case STATE_NEW_PACKET: + /* Make sure we've got enough data */ + if(bytestream_size(stream) < module->max_frame_size && + !(flags & VC_PACKETIZER_FLAG_FLUSH)) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + if(!bytestream_size(stream)) + return VC_CONTAINER_ERROR_INCOMPLETE_DATA; + + module->frame_size = bytestream_size(stream); + if(module->frame_size > module->max_frame_size) + module->frame_size = module->max_frame_size; + bytestream_get_timestamps_and_offset(stream, &pts, &dts, &offset, true); + vc_container_time_set(time, pts); + if(pts != VC_CONTAINER_TIME_UNKNOWN) + vc_container_time_add(time, offset / module->bytes_per_sample); + + module->bytes_read = 0; + module->state = STATE_DATA; + /* fall through to the next state */ + + case STATE_DATA: + size = module->frame_size - module->bytes_read; + out->pts = out->dts = VC_CONTAINER_TIME_UNKNOWN; + out->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END; + out->size = (size * module->conversion_factor) >> FACTOR_SHIFT; + + if(!module->bytes_read) + { + out->pts = out->dts = vc_container_time_get(time); + out->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + } + + if(flags & VC_PACKETIZER_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + if(flags & VC_PACKETIZER_FLAG_SKIP) + { + bytestream_skip( stream, size ); + } + else + { + out->size = MIN(out->size, out->buffer_size); + size = (out->size << FACTOR_SHIFT) / module->conversion_factor; + out->size = (size * module->conversion_factor) >> FACTOR_SHIFT; + + if(module->conversion != CONVERSION_NONE) + convert_pcm(p_ctx, stream, size, out->data); + else + bytestream_get(stream, out->data, out->size); + } + module->bytes_read += size; + + if(module->bytes_read == module->frame_size) + { + vc_container_time_add(time, module->samples_per_frame); + module->state = STATE_NEW_PACKET; + } + return VC_CONTAINER_SUCCESS; + + default: + break; + }; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T pcm_packetizer_open( VC_PACKETIZER_T *p_ctx ) +{ + VC_PACKETIZER_MODULE_T *module; + unsigned int bytes_per_sample = 0; + enum conversion conversion = CONVERSION_NONE; + + if(p_ctx->in->codec != VC_CONTAINER_CODEC_PCM_UNSIGNED_BE && + p_ctx->in->codec != VC_CONTAINER_CODEC_PCM_UNSIGNED_LE && + p_ctx->in->codec != VC_CONTAINER_CODEC_PCM_SIGNED_BE && + p_ctx->in->codec != VC_CONTAINER_CODEC_PCM_SIGNED_LE && + p_ctx->in->codec != VC_CONTAINER_CODEC_PCM_FLOAT_BE && + p_ctx->in->codec != VC_CONTAINER_CODEC_PCM_FLOAT_LE) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if(p_ctx->in->type->audio.block_align) + bytes_per_sample = p_ctx->in->type->audio.block_align; + else if(p_ctx->in->type->audio.bits_per_sample && p_ctx->in->type->audio.channels) + bytes_per_sample = p_ctx->in->type->audio.bits_per_sample * + p_ctx->in->type->audio.channels / 8; + + if(!bytes_per_sample) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Check if we support any potential conversion we've been asked to do */ + if(p_ctx->out->codec_variant) + conversion = CONVERSION_UNKNOWN; + if(p_ctx->out->codec_variant == VC_FOURCC('s','1','6','l') && + p_ctx->in->codec == VC_CONTAINER_CODEC_PCM_SIGNED_LE && + p_ctx->in->type->audio.bits_per_sample == 16) + conversion = CONVERSION_NONE; + if(p_ctx->out->codec_variant == VC_FOURCC('s','1','6','l') && + (p_ctx->in->codec == VC_CONTAINER_CODEC_PCM_UNSIGNED_LE || + p_ctx->in->codec == VC_CONTAINER_CODEC_PCM_UNSIGNED_BE) && + p_ctx->in->type->audio.bits_per_sample == 8) + conversion = CONVERSION_U8_TO_S16L; + if(conversion == CONVERSION_UNKNOWN) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + p_ctx->priv->module = module = malloc(sizeof(*module)); + if(!module) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + memset(module, 0, sizeof(*module)); + module->conversion = conversion; + module->conversion_factor = 1 << FACTOR_SHIFT; + + p_ctx->out->codec_variant = 0; + if(conversion == CONVERSION_U8_TO_S16L) + { + module->conversion_factor = 2 << FACTOR_SHIFT; + p_ctx->out->type->audio.bits_per_sample *= 2; + p_ctx->out->type->audio.block_align *= 2; + p_ctx->out->codec = VC_CONTAINER_CODEC_PCM_SIGNED_LE; + } + + vc_container_time_set_samplerate(&p_ctx->priv->time, p_ctx->in->type->audio.sample_rate, 1); + + p_ctx->max_frame_size = FRAME_SIZE; + module->max_frame_size = (FRAME_SIZE << FACTOR_SHIFT) / module->conversion_factor; + module->bytes_per_sample = bytes_per_sample; + module->samples_per_frame = module->max_frame_size / bytes_per_sample; + p_ctx->priv->pf_close = pcm_packetizer_close; + p_ctx->priv->pf_packetize = pcm_packetizer_packetize; + p_ctx->priv->pf_reset = pcm_packetizer_reset; + + LOG_DEBUG(0, "using pcm audio packetizer"); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_PACKETIZER_REGISTER(pcm_packetizer_open, "pcm"); diff --git a/containers/qsynth/CMakeLists.txt b/containers/qsynth/CMakeLists.txt new file mode 100644 index 0000000..b188e50 --- /dev/null +++ b/containers/qsynth/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_qsynth ${LIBRARY_TYPE} qsynth_reader.c) + +target_link_libraries(reader_qsynth containers) + +install(TARGETS reader_qsynth DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/qsynth/qsynth_reader.c b/containers/qsynth/qsynth_reader.c new file mode 100644 index 0000000..06a9d54 --- /dev/null +++ b/containers/qsynth/qsynth_reader.c @@ -0,0 +1,482 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Defines. +******************************************************************************/ + +#define BI32(b) (((b)[0]<<24)|((b)[1]<<16)|((b)[2]<<8)|((b)[3])) +#define BI16(b) (((b)[0]<<8)|((b)[1])) + +#define HEADER_LENGTH 14 +#define MAX_TRACKS 128 + +/****************************************************************************** +Type definitions +******************************************************************************/ +struct _QSYNTH_SEGMENT_T { + struct _QSYNTH_SEGMENT_T *next; + uint32_t len; + uint8_t *data; +}; +typedef struct _QSYNTH_SEGMENT_T QSYNTH_SEGMENT_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + uint32_t filesize; + QSYNTH_SEGMENT_T *seg; + QSYNTH_SEGMENT_T *pass; + uint32_t sent; + int64_t timestamp; + uint32_t seek; +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T qsynth_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +static VC_CONTAINER_STATUS_T qsynth_read_header(uint8_t *data, uint32_t *tracks, + uint32_t *division, uint8_t *fps, uint8_t *dpf) +{ + if(data[0] != 'M' || data[1] != 'T' || data[2] != 'h' || data[3] != 'd' || + data[4] != 0 || data[5] != 0 || data[6] != 0 || data[7] != 6) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if(data[12] < 0x80) + { + if(division) *division = BI16(data+12); + } + else + { + if(fps) *fps = 256-data[12]; + if(dpf) *dpf = data[13]; + } + + if(tracks) *tracks = BI16(data+10); + + return VC_CONTAINER_SUCCESS; +} + +static int qsynth_read_variable(uint8_t *data, uint32_t *val) +{ + int i = 0; + *val = 0; + do { + *val = (*val << 7) + (data[i] & 0x7f); + } while(data[i++] & 0x80); + + return i; +} + +static VC_CONTAINER_STATUS_T qsynth_read_event(uint8_t *data, uint32_t *used, uint8_t *last, + uint32_t *time, uint32_t *tempo, uint32_t *end) +{ + int read; + + // need at least 4 bytes here + read = qsynth_read_variable(data, time); + + if(data[read] == 0xff) // meta event + { + uint32_t len; + uint8_t type = data[read+1]; + + read += 2; + read += qsynth_read_variable(data+read, &len); + + if(type == 0x2f) // end of track + { + if(len != 0) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + *end = 1; + } + else if(type == 0x51) // tempo event + { + if(len != 3) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + *tempo = (data[read]<<16) | (data[read+1]<<8) | data[read+2]; + } + + read += len; + } + else if(data[read] == 0xf0 || data[read] == 0xf7) // sysex events + { + uint32_t len; + read += 1; + read += qsynth_read_variable(data+read, &len) + len; + } + else // midi event + { + uint8_t type; + + if(data[read] < 128) + type = *last; + else + { + type = data[read] >> 4; + *last = type; + read++; + } + + switch(type) { + case 8: case 9: case 0xa: case 0xb: case 0xe: + read += 2; + break; + case 0xc: case 0xd: + read += 1; + break; + default: + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + } + + *used = read; + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T qsynth_read_track(QSYNTH_SEGMENT_T *seg, + uint32_t *ticks, int64_t *time, + uint32_t *us_perclock, uint32_t *tempo_ticks) +{ + uint32_t total_ticks = 0; + uint32_t used = 8; + uint8_t last = 0; + + *time = 0LL; + *tempo_ticks = 0; + + while(used < seg->len) + { + VC_CONTAINER_STATUS_T status; + uint32_t event_ticks, new_tempo = 0, end = 0, event_used; + if((status = qsynth_read_event(seg->data+used, &event_used, &last, &event_ticks, &new_tempo, &end)) != VC_CONTAINER_SUCCESS) + return status; + + used += event_used; + total_ticks += event_ticks; + + if(new_tempo != 0) + { + *time += ((int64_t) (total_ticks - *tempo_ticks)) * (*us_perclock); + *us_perclock = new_tempo; + *tempo_ticks = total_ticks; + } + + if(end) + break; + } + + *ticks = total_ticks; + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T qsynth_get_duration(VC_CONTAINER_T *p_ctx) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + QSYNTH_SEGMENT_T **seg = &(module->seg); + uint32_t i, tracks, division = 0, max_ticks = 0, us_perclock = 500000; + uint32_t end_uspc = 0, end_ticks = 0; + int64_t end_time = 0; + uint8_t fps = 1, dpf = 1; + + if((*seg = malloc(sizeof(QSYNTH_SEGMENT_T) + HEADER_LENGTH)) == NULL) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + (*seg)->next = NULL; + (*seg)->len = HEADER_LENGTH; + (*seg)->data = (uint8_t *) ((*seg) + 1); + + if(PEEK_BYTES(p_ctx, (*seg)->data, HEADER_LENGTH) != HEADER_LENGTH) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if((status = qsynth_read_header((*seg)->data, &tracks, &division, &fps, &dpf)) != VC_CONTAINER_SUCCESS) + return status; + + // if we have a suspiciously large number of tracks, this is probably a bad file + if(tracks > MAX_TRACKS) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + SKIP_BYTES(p_ctx, HEADER_LENGTH); + + seg = &((*seg)->next); + module->filesize = HEADER_LENGTH; + + if(division == 0) + { + us_perclock = 1000000 / (fps * dpf); + division = 1; + } + + for(i=0; i (1<<20) || (*seg = malloc(sizeof(QSYNTH_SEGMENT_T) + 8 + len)) == NULL) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + module->filesize += len+8; + (*seg)->next = NULL; + (*seg)->len = len + 8; + (*seg)->data = (uint8_t *) ((*seg) + 1); + + memcpy((*seg)->data, dummy, 8); + if(READ_BYTES(p_ctx, (*seg)->data+8, len) != len) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + if((status = qsynth_read_track(*seg, &ticks, &time, &us_perclock, &tempo_ticks)) != VC_CONTAINER_SUCCESS) + return status; + + if(end_uspc == 0) + { + end_uspc = us_perclock; + end_ticks = tempo_ticks; + end_time = time; + } + + if(ticks > max_ticks) + max_ticks = ticks; + + seg = &((*seg)->next); + } + + if(end_uspc == 0) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + module->pass = module->seg; + module->sent = 0; + p_ctx->duration = (end_time + (((int64_t) (max_ticks - end_ticks)) * end_uspc)) / division; + module->track->format->extradata = (uint8_t *) &module->filesize; + module->track->format->extradata_size = 4; + return VC_CONTAINER_SUCCESS; +} + + +/***************************************************************************** +Functions exported as part of the Container Module API +*****************************************************************************/ +static VC_CONTAINER_STATUS_T qsynth_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet, + uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + if(module->pass) + { + packet->size = module->pass->len - module->sent; + packet->dts = packet->pts = 0; + packet->track = 0; + packet->flags = module->sent ? 0 : VC_CONTAINER_PACKET_FLAG_FRAME_START; + } + else + { + if(module->timestamp > p_ctx->duration) + return VC_CONTAINER_ERROR_EOS; + + packet->size = 5; + packet->dts = packet->pts = module->timestamp; + packet->track = 0; + packet->flags = VC_CONTAINER_PACKET_FLAG_FRAME; + } + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + { + if(module->pass) + { + module->pass = module->pass->next; + module->sent = 0; + } + else + { + // if we're playing then we can't really skip, but have to simulate a seek instead + module->seek = 1; + module->timestamp += 40; + } + + return VC_CONTAINER_SUCCESS; + } + + if(flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + // read frame into packet->data + if(module->pass) + { + uint32_t copy = MIN(packet->size, packet->buffer_size); + memcpy(packet->data, module->pass->data + module->sent, copy); + packet->size = copy; + + if((module->sent += copy) == module->pass->len) + { + module->pass = module->pass->next; + module->sent = 0; + packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + } + } + else + { + if(packet->buffer_size < packet->size) + return VC_CONTAINER_ERROR_BUFFER_TOO_SMALL; + + if(module->seek) + { + uint32_t current_time = module->timestamp / 1000; + + packet->data[0] = 'S'; + packet->data[1] = (uint8_t)((current_time >> 24) & 0xFF); + packet->data[2] = (uint8_t)((current_time >> 16) & 0xFF); + packet->data[3] = (uint8_t)((current_time >> 8) & 0xFF); + packet->data[4] = (uint8_t)((current_time ) & 0xFF); + module->seek = 0; + } + else + { + packet->data[0] = 'P'; + packet->data[1] = 0; + packet->data[2] = 0; + packet->data[3] = 0; + packet->data[4] = 40; + module->timestamp += 40 * 1000; + } + } + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T qsynth_reader_seek( VC_CONTAINER_T *p_ctx, + int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, + VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_PARAM_UNUSED(flags); + + if (mode != VC_CONTAINER_SEEK_MODE_TIME) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + + if(*offset < 0) + *offset = 0; + else if(*offset > p_ctx->duration) + *offset = p_ctx->duration; + + module->timestamp = *offset; + module->seek = 1; + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T qsynth_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + QSYNTH_SEGMENT_T *seg = module->seg; + for(; p_ctx->tracks_num > 0; p_ctx->tracks_num--) + vc_container_free_track(p_ctx, p_ctx->tracks[p_ctx->tracks_num-1]); + while(seg != NULL) + { + QSYNTH_SEGMENT_T *next = seg->next; + free(seg); + seg = next; + } + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T qsynth_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + uint8_t header[HEADER_LENGTH]; + + /* Check the file header */ + if((PEEK_BYTES(p_ctx, header, HEADER_LENGTH) != HEADER_LENGTH) || + qsynth_read_header(header, 0, 0, 0, 0) != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks_num = 1; + p_ctx->tracks = &module->track; + p_ctx->tracks[0] = vc_container_allocate_track(p_ctx, 0); + if(!p_ctx->tracks[0]) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + p_ctx->tracks[0]->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + p_ctx->tracks[0]->format->codec = VC_CONTAINER_CODEC_MIDI; + p_ctx->tracks[0]->is_enabled = true; + + if((status = qsynth_get_duration(p_ctx)) != VC_CONTAINER_SUCCESS) goto error; + + LOG_DEBUG(p_ctx, "using qsynth reader"); + + p_ctx->capabilities = VC_CONTAINER_CAPS_CAN_SEEK; + + p_ctx->priv->pf_close = qsynth_reader_close; + p_ctx->priv->pf_read = qsynth_reader_read; + p_ctx->priv->pf_seek = qsynth_reader_seek; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "qsynth: error opening stream (%i)", status); + if(module) qsynth_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function +********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open qsynth_reader_open +#endif diff --git a/containers/raw/CMakeLists.txt b/containers/raw/CMakeLists.txt new file mode 100644 index 0000000..0ada17e --- /dev/null +++ b/containers/raw/CMakeLists.txt @@ -0,0 +1,18 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_raw_video ${LIBRARY_TYPE} raw_video_reader.c) + +target_link_libraries(reader_raw_video containers) + +install(TARGETS reader_raw_video DESTINATION ${VMCS_PLUGIN_DIR}) + +add_library(writer_raw_video ${LIBRARY_TYPE} raw_video_writer.c) + +target_link_libraries(writer_raw_video containers) + +install(TARGETS writer_raw_video DESTINATION ${VMCS_PLUGIN_DIR}) diff --git a/containers/raw/raw_video_common.h b/containers/raw/raw_video_common.h new file mode 100644 index 0000000..52d73ec --- /dev/null +++ b/containers/raw/raw_video_common.h @@ -0,0 +1,66 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef RAW_VIDEO_COMMON_H +#define RAW_VIDEO_COMMON_H + +static struct { + const char *id; + VC_CONTAINER_FOURCC_T codec; + unsigned int size_num; + unsigned int size_den; +} table[] = { + {"420", VC_CONTAINER_CODEC_I420, 3, 2}, + {0, 0, 0, 0} +}; + +STATIC_INLINE bool from_yuv4mpeg2(const char *id, VC_CONTAINER_FOURCC_T *codec, + unsigned int *size_num, unsigned int *size_den) +{ + unsigned int i; + for (i = 0; table[i].id; i++) + if (!strcmp(id, table[i].id)) + break; + if (codec) *codec = table[i].codec; + if (size_num) *size_num = table[i].size_num; + if (size_den) *size_den = table[i].size_den; + return !!table[i].id; +} + +STATIC_INLINE bool to_yuv4mpeg2(VC_CONTAINER_FOURCC_T codec, const char **id, + unsigned int *size_num, unsigned int *size_den) +{ + unsigned int i; + for (i = 0; table[i].id; i++) + if (codec == table[i].codec) + break; + if (id) *id = table[i].id; + if (size_num) *size_num = table[i].size_num; + if (size_den) *size_den = table[i].size_den; + return !!table[i].id; +} + +#endif /* RAW_VIDEO_COMMON_H */ diff --git a/containers/raw/raw_video_reader.c b/containers/raw/raw_video_reader.c new file mode 100644 index 0000000..00ac064 --- /dev/null +++ b/containers/raw/raw_video_reader.c @@ -0,0 +1,465 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +#include "raw_video_common.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define FILE_HEADER_SIZE_MAX 1024 +#define FRAME_HEADER_SIZE_MAX 256 +#define OPTION_SIZE_MAX 32 + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + VC_CONTAINER_STATUS_T status; + + bool yuv4mpeg2; + bool non_standard; + char option[OPTION_SIZE_MAX]; + + bool frame_header; + unsigned int frame_header_size; + + int64_t data_offset; + unsigned int block_size; + unsigned int block_offset; + unsigned int frames; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T rawvideo_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ +static VC_CONTAINER_STATUS_T read_yuv4mpeg2_option( VC_CONTAINER_T *ctx, + unsigned int *bytes_left ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + unsigned int size, i; + + /* Start by skipping spaces */ + while (*bytes_left && PEEK_U8(ctx) == ' ') + (*bytes_left)--, _SKIP_U8(ctx); + + size = PEEK_BYTES(ctx, module->option, + MIN(sizeof(module->option), *bytes_left)); + + /* The config option ends at next space or newline */ + for (i = 0; i < size; i++) + { + if (module->option[i] == ' ' || module->option[i] == 0x0a) + { + module->option[i] = 0; + break; + } + } + if (i == 0) + return VC_CONTAINER_ERROR_NOT_FOUND; + + *bytes_left -= i; + SKIP_BYTES(ctx, i); + + /* If option is too long, we just discard it */ + if (i == size) + { + while (*bytes_left && PEEK_U8(ctx) != ' ' && PEEK_U8(ctx) != 0x0a) + (*bytes_left)--, _SKIP_U8(ctx); + return VC_CONTAINER_ERROR_NOT_FOUND; + } + + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T read_yuv4mpeg2_file_header( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + unsigned int bytes_left = FILE_HEADER_SIZE_MAX - 10; + unsigned int value1, value2; + char codec[OPTION_SIZE_MAX] = "420"; + uint8_t h[10]; + + /* Check for the YUV4MPEG2 signature */ + if (READ_BYTES(ctx, h, sizeof(h)) != sizeof(h)) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if (memcmp(h, "YUV4MPEG2 ", sizeof(h))) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Parse parameters */ + while (read_yuv4mpeg2_option(ctx, &bytes_left) == VC_CONTAINER_SUCCESS) + { + if (sscanf(module->option, "W%i", &value1) == 1) + ctx->tracks[0]->format->type->video.width = value1; + else if (sscanf(module->option, "H%i", &value1) == 1) + ctx->tracks[0]->format->type->video.height = value1; + else if (sscanf(module->option, "S%i", &value1) == 1) + module->block_size = value1; + else if (sscanf(module->option, "F%i:%i", &value1, &value2) == 2) + { + ctx->tracks[0]->format->type->video.frame_rate_num = value1; + ctx->tracks[0]->format->type->video.frame_rate_den = value2; + } + else if (sscanf(module->option, "A%i:%i", &value1, &value2) == 2) + { + ctx->tracks[0]->format->type->video.par_num = value1; + ctx->tracks[0]->format->type->video.par_den = value2; + } + else if (module->option[0] == 'C') + { + strcpy(codec, module->option+1); + } + } + + /* Check the end marker */ + if (_READ_U8(ctx) != 0x0a) + { + LOG_ERROR(ctx, "missing end of header marker"); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + /* Find out which codec we are dealing with */ + if (from_yuv4mpeg2(codec, &ctx->tracks[0]->format->codec, &value1, &value2)) + { + module->block_size = ctx->tracks[0]->format->type->video.width * + ctx->tracks[0]->format->type->video.height * value1 / value2; + } + else + { + memcpy(&ctx->tracks[0]->format->codec, codec, 4); + module->non_standard = true; + } + + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T read_yuv4mpeg2_frame_header( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + unsigned int bytes_left = FRAME_HEADER_SIZE_MAX - 5; + unsigned int value1; + char header[5]; + + if (READ_BYTES(ctx, header, sizeof(header)) != sizeof(header) || + memcmp(header, "FRAME", sizeof(header))) + { + LOG_ERROR(ctx, "missing frame marker"); + return STREAM_STATUS(ctx) != VC_CONTAINER_SUCCESS ? + STREAM_STATUS(ctx) : VC_CONTAINER_ERROR_CORRUPTED; + } + + /* Parse parameters */ + while (read_yuv4mpeg2_option(ctx, &bytes_left) == VC_CONTAINER_SUCCESS) + { + if (module->non_standard && sscanf(module->option, "S%i", &value1) == 1) + module->block_size = value1; + } + + /* Check the end marker */ + if (_READ_U8(ctx) != 0x0a) + { + LOG_ERROR(ctx, "missing end of frame header marker"); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + module->frame_header_size = FRAME_HEADER_SIZE_MAX - bytes_left - 1; + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T rawvideo_parse_uri( VC_CONTAINER_T *ctx, + VC_CONTAINER_FOURCC_T *c, unsigned int *w, unsigned int *h, + unsigned int *fr_num, unsigned int *fr_den, unsigned *block_size ) +{ + VC_CONTAINER_FOURCC_T codec = 0; + unsigned int i, matches, width = 0, height = 0, fn = 0, fd = 0, size = 0; + const char *uri = ctx->priv->io->uri; + + /* Try and find a match for the string describing the format */ + for (i = 0; uri[i]; i++) + { + if (uri[i] != '_' && uri[i+1] != 'C') + continue; + + matches = sscanf(uri+i, "_C%4cW%iH%iF%i#%iS%i", (char *)&codec, + &width, &height, &fn, &fd, &size); + if (matches >= 3) + break; + } + if (!uri[i]) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if (!size) + { + switch (codec) + { + case VC_CONTAINER_CODEC_I420: + case VC_CONTAINER_CODEC_YV12: + size = width * height * 3 / 2; + break; + default: break; + } + } + + if (!width || !height || !size) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if (block_size) *block_size = size; + if (c) *c = codec; + if (w) *w = width; + if (h) *h = height; + if (fr_num) *fr_num = fn; + if (fr_den) *fr_den = fd; + if (block_size) *block_size = size; + + return VC_CONTAINER_SUCCESS; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T rawvideo_reader_read( VC_CONTAINER_T *ctx, + VC_CONTAINER_PACKET_T *packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + unsigned int size; + + if (module->status != VC_CONTAINER_SUCCESS) + return module->status; + + if (module->yuv4mpeg2 && !module->block_offset && + !module->frame_header) + { + module->status = read_yuv4mpeg2_frame_header(ctx); + if (module->status != VC_CONTAINER_SUCCESS) + return module->status; + + module->frame_header = true; + } + + if (!module->block_offset) + packet->pts = packet->dts = module->frames * INT64_C(1000000) * + ctx->tracks[0]->format->type->video.frame_rate_den / + ctx->tracks[0]->format->type->video.frame_rate_num; + else + packet->pts = packet->dts = VC_CONTAINER_TIME_UNKNOWN; + packet->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END | + VC_CONTAINER_PACKET_FLAG_KEYFRAME; + if (!module->block_offset) + packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + packet->frame_size = module->block_size; + packet->size = module->block_size - module->block_offset; + packet->track = 0; + + if (flags & VC_CONTAINER_READ_FLAG_SKIP) + { + size = SKIP_BYTES(ctx, packet->size); + module->block_offset = 0; + module->frames++; + module->frame_header = 0; + module->status = STREAM_STATUS(ctx); + return module->status; + } + + if (flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + size = MIN(module->block_size - module->block_offset, packet->buffer_size); + size = READ_BYTES(ctx, packet->data, size); + module->block_offset += size; + packet->size = size; + + if (module->block_offset == module->block_size) + { + module->block_offset = 0; + module->frame_header = 0; + module->frames++; + } + + module->status = size ? VC_CONTAINER_SUCCESS : STREAM_STATUS(ctx); + return module->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rawvideo_reader_seek( VC_CONTAINER_T *ctx, int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + VC_CONTAINER_PARAM_UNUSED(mode); + + module->frames = *offset * + ctx->tracks[0]->format->type->video.frame_rate_num / + ctx->tracks[0]->format->type->video.frame_rate_den / INT64_C(1000000); + module->block_offset = 0; + + if ((flags & VC_CONTAINER_SEEK_FLAG_FORWARD) && + module->frames * INT64_C(1000000) * + ctx->tracks[0]->format->type->video.frame_rate_den / + ctx->tracks[0]->format->type->video.frame_rate_num < *offset) + module->frames++; + + module->frame_header = 0; + + module->status = + SEEK(ctx, module->data_offset + module->frames * + (module->block_size + module->frame_header_size)); + + return module->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rawvideo_reader_close( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + for (; ctx->tracks_num > 0; ctx->tracks_num--) + vc_container_free_track(ctx, ctx->tracks[ctx->tracks_num-1]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T rawvideo_reader_open( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + const char *extension = vc_uri_path_extension(ctx->priv->uri); + VC_CONTAINER_MODULE_T *module = 0; + bool yuv4mpeg2 = false; + uint8_t h[10]; + + /* Check if the user has specified a container */ + vc_uri_find_query(ctx->priv->uri, 0, "container", &extension); + + /* Check for the YUV4MPEG2 signature */ + if (PEEK_BYTES(ctx, h, sizeof(h)) != sizeof(h)) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if (!memcmp(h, "YUV4MPEG2 ", sizeof(h))) + yuv4mpeg2 = true; + + /* Or check if the extension is supported */ + if (!yuv4mpeg2 && + !(extension && !strcasecmp(extension, "yuv"))) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + LOG_DEBUG(ctx, "using raw video reader"); + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if (!module) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + memset(module, 0, sizeof(*module)); + ctx->priv->module = module; + ctx->tracks_num = 1; + ctx->tracks = &module->track; + ctx->tracks[0] = vc_container_allocate_track(ctx, 0); + if (!ctx->tracks[0]) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto error; + } + ctx->tracks[0]->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + ctx->tracks[0]->is_enabled = true; + ctx->tracks[0]->format->type->video.frame_rate_num = 25; + ctx->tracks[0]->format->type->video.frame_rate_den = 1; + ctx->tracks[0]->format->type->video.par_num = 1; + ctx->tracks[0]->format->type->video.par_den = 1; + + if (yuv4mpeg2) + { + status = read_yuv4mpeg2_file_header(ctx); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + module->data_offset = STREAM_POSITION(ctx); + + status = read_yuv4mpeg2_frame_header(ctx); + if (status != VC_CONTAINER_SUCCESS) + goto error; + module->frame_header = true; + } + else + { + VC_CONTAINER_FOURCC_T codec; + unsigned int width, height, fr_num, fr_den, block_size; + + status = rawvideo_parse_uri(ctx, &codec, &width, &height, + &fr_num, &fr_den, &block_size); + if (status != VC_CONTAINER_SUCCESS) + goto error; + ctx->tracks[0]->format->codec = codec; + ctx->tracks[0]->format->type->video.width = width; + ctx->tracks[0]->format->type->video.height = height; + if (fr_num && fr_den) + { + ctx->tracks[0]->format->type->video.frame_rate_num = fr_num; + ctx->tracks[0]->format->type->video.frame_rate_den = fr_den; + } + module->block_size = block_size; + } + + /* + * We now have all the information we really need to start playing the stream + */ + + LOG_INFO(ctx, "rawvideo %4.4s/%ix%i/fps:%i:%i/size:%i", + (char *)&ctx->tracks[0]->format->codec, + ctx->tracks[0]->format->type->video.width, + ctx->tracks[0]->format->type->video.height, + ctx->tracks[0]->format->type->video.frame_rate_num, + ctx->tracks[0]->format->type->video.frame_rate_den, module->block_size); + ctx->priv->pf_close = rawvideo_reader_close; + ctx->priv->pf_read = rawvideo_reader_read; + ctx->priv->pf_seek = rawvideo_reader_seek; + module->yuv4mpeg2 = yuv4mpeg2; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(ctx, "rawvideo: error opening stream (%i)", status); + rawvideo_reader_close(ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open rawvideo_reader_open +#endif diff --git a/containers/raw/raw_video_writer.c b/containers/raw/raw_video_writer.c new file mode 100644 index 0000000..49b7c7c --- /dev/null +++ b/containers/raw/raw_video_writer.c @@ -0,0 +1,264 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +#include "raw_video_common.h" + +/****************************************************************************** +Defines. +******************************************************************************/ + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + bool yuv4mpeg2; + bool header_done; + bool non_standard; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T rawvideo_writer_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ +static VC_CONTAINER_STATUS_T rawvideo_write_header( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + unsigned int size; + char line[128]; + const char *id; + + size = snprintf(line, sizeof(line), "YUV4MPEG2 W%i H%i", + ctx->tracks[0]->format->type->video.width, + ctx->tracks[0]->format->type->video.height); + if (size >= sizeof(line)) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + WRITE_BYTES(ctx, line, size); + + if (ctx->tracks[0]->format->type->video.frame_rate_num && + ctx->tracks[0]->format->type->video.frame_rate_den) + { + size = snprintf(line, sizeof(line), " F%i:%i", + ctx->tracks[0]->format->type->video.frame_rate_num, + ctx->tracks[0]->format->type->video.frame_rate_den); + if (size >= sizeof(line)) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + WRITE_BYTES(ctx, line, size); + } + + if (ctx->tracks[0]->format->type->video.par_num && + ctx->tracks[0]->format->type->video.par_den) + { + size = snprintf(line, sizeof(line), " A%i:%i", + ctx->tracks[0]->format->type->video.par_num, + ctx->tracks[0]->format->type->video.par_den); + if (size >= sizeof(line)) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + WRITE_BYTES(ctx, line, size); + } + + if (to_yuv4mpeg2(ctx->tracks[0]->format->codec, &id, 0, 0)) + { + size = snprintf(line, sizeof(line), " C%s", id); + } + else + { + module->non_standard = true; + size = snprintf(line, sizeof(line), " C%4.4s", + (char *)&ctx->tracks[0]->format->codec); + } + if (size >= sizeof(line)) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + WRITE_BYTES(ctx, line, size); + + _WRITE_U8(ctx, 0x0a); + module->header_done = true; + return STREAM_STATUS(ctx); +} + +static VC_CONTAINER_STATUS_T simple_write_add_track( VC_CONTAINER_T *ctx, + VC_CONTAINER_ES_FORMAT_T *format ) +{ + VC_CONTAINER_STATUS_T status; + + /* Sanity check that we support the type of track being created */ + if (ctx->tracks_num) + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + if (format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) + return VC_CONTAINER_ERROR_TRACK_FORMAT_NOT_SUPPORTED; + + /* Allocate and initialise track data */ + ctx->tracks[0] = vc_container_allocate_track(ctx, 0); + if (!ctx->tracks[0]) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + status = vc_container_track_allocate_extradata(ctx, + ctx->tracks[0], format->extradata_size); + if(status != VC_CONTAINER_SUCCESS) + return status; + + vc_container_format_copy(ctx->tracks[0]->format, format, + format->extradata_size); + ctx->tracks_num++; + return VC_CONTAINER_SUCCESS; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T rawvideo_writer_close( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + for (; ctx->tracks_num > 0; ctx->tracks_num--) + vc_container_free_track(ctx, ctx->tracks[ctx->tracks_num-1]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rawvideo_writer_write( VC_CONTAINER_T *ctx, + VC_CONTAINER_PACKET_T *packet ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + VC_CONTAINER_STATUS_T status; + + if (module->yuv4mpeg2 && !module->header_done) + { + status = rawvideo_write_header(ctx); + if (status != VC_CONTAINER_SUCCESS) + return status; + } + + if (module->yuv4mpeg2 && + (packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_START)) + { + /* Write the metadata */ + WRITE_BYTES(ctx, "FRAME", sizeof("FRAME")-1); + + /* For formats not supported by the YUV4MPEG2 spec, we prepend + * each frame with its size */ + if (module->non_standard) + { + unsigned int size; + char line[32]; + size = snprintf(line, sizeof(line), " S%i", + packet->frame_size ? packet->frame_size : packet->size); + if (size < sizeof(line)) + WRITE_BYTES(ctx, line, size); + } + + _WRITE_U8(ctx, 0x0a); + } + + /* Write the elementary stream */ + WRITE_BYTES(ctx, packet->data, packet->size); + + return STREAM_STATUS(ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rawvideo_writer_control( VC_CONTAINER_T *ctx, + VC_CONTAINER_CONTROL_T operation, va_list args ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + VC_CONTAINER_ES_FORMAT_T *format; + + switch (operation) + { + case VC_CONTAINER_CONTROL_TRACK_ADD: + format = (VC_CONTAINER_ES_FORMAT_T *)va_arg(args, VC_CONTAINER_ES_FORMAT_T *); + return simple_write_add_track(ctx, format); + + case VC_CONTAINER_CONTROL_TRACK_ADD_DONE: + return module->yuv4mpeg2 ? + rawvideo_write_header( ctx ) : VC_CONTAINER_SUCCESS; + + default: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + } +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T rawvideo_writer_open( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + const char *extension = vc_uri_path_extension(ctx->priv->uri); + VC_CONTAINER_MODULE_T *module; + bool yuv4mpeg2 = false; + + /* Check if the user has specified a container */ + vc_uri_find_query(ctx->priv->uri, 0, "container", &extension); + + /* Check we're the right writer for this */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(!strcasecmp(extension, "y4m") || !strcasecmp(extension, "yuv4mpeg2")) + yuv4mpeg2 = true; + if(!yuv4mpeg2 && strcasecmp(extension, "yuv")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + LOG_DEBUG(ctx, "using rawvideo writer"); + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if (!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + ctx->priv->module = module; + ctx->tracks = &module->track; + module->yuv4mpeg2 = yuv4mpeg2; + + ctx->priv->pf_close = rawvideo_writer_close; + ctx->priv->pf_write = rawvideo_writer_write; + ctx->priv->pf_control = rawvideo_writer_control; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(ctx, "rawvideo: error opening stream (%i)", status); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak writer_open rawvideo_writer_open +#endif diff --git a/containers/rcv/CMakeLists.txt b/containers/rcv/CMakeLists.txt new file mode 100644 index 0000000..9407f58 --- /dev/null +++ b/containers/rcv/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_rcv ${LIBRARY_TYPE} rcv_reader.c) + +target_link_libraries(reader_rcv containers) + +install(TARGETS reader_rcv DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/rcv/rcv_reader.c b/containers/rcv/rcv_reader.c new file mode 100644 index 0000000..4d4db27 --- /dev/null +++ b/containers/rcv/rcv_reader.c @@ -0,0 +1,358 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_index.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Defines. +******************************************************************************/ + +#define LI32(b) (((b)[3]<<24)|((b)[2]<<16)|((b)[1]<<8)|((b)[0])) +#define LI24(b) (((b)[2]<<16)|((b)[1]<<8)|((b)[0])) + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct { + unsigned int num_frames : 24; + unsigned int constant_c5 : 8; + int constant_4; + uint32_t struct_c; + uint32_t vert_size; + uint32_t horiz_size; + int constant_c; + uint32_t struct_b[2]; + uint32_t framerate; +} RCV_FILE_HEADER_T; + +typedef struct { + unsigned int framesize : 24; + unsigned int res : 7; + unsigned int keyframe : 1; + uint32_t timestamp; +} RCV_FRAME_HEADER_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + uint8_t extradata[4]; + uint8_t mid_frame; + uint32_t frame_read; + RCV_FRAME_HEADER_T frame; + VC_CONTAINER_INDEX_T *index; /* index of key frames */ + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T rcv_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +static VC_CONTAINER_STATUS_T rcv_read_header(VC_CONTAINER_T *p_ctx) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + RCV_FILE_HEADER_T header; + uint8_t dummy[36]; + + if(PEEK_BYTES(p_ctx, dummy, sizeof(dummy)) != sizeof(dummy)) return VC_CONTAINER_ERROR_EOS; + + header.num_frames = LI24(dummy); + header.constant_c5 = dummy[3]; + header.constant_4 = LI32(dummy+4); + + // extradata is just struct_c from the header + memcpy(module->extradata, dummy+8, 4); + module->track->format->extradata = module->extradata; + module->track->format->extradata_size = 4; + + module->track->format->type->video.height = LI32(dummy+12); + module->track->format->type->video.width = LI32(dummy+16); + + header.constant_c = LI32(dummy+20); + memcpy(header.struct_b, dummy+24, 8); + header.framerate = LI32(dummy+32); + + if(header.constant_c5 != 0xc5 || header.constant_4 != 0x4 || header.constant_c != 0xc) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if(header.framerate != 0 && header.framerate != 0xffffffffUL) + { + module->track->format->type->video.frame_rate_num = header.framerate; + module->track->format->type->video.frame_rate_den = 1; + } + + // fill in general information + if(header.num_frames != (1<<24)-1 && header.framerate != 0 && header.framerate != 0xffffffffUL) + p_ctx->duration = ((int64_t) header.num_frames * 1000000LL) / (int64_t) header.framerate; + + // we're happy that this is an rcv file + SKIP_BYTES(p_ctx, sizeof(dummy)); + + return STREAM_STATUS(p_ctx); +} + +/***************************************************************************** + * Utility function to seek to the keyframe nearest the given timestamp. + * + * @param p_ctx Pointer to the container context. + * @param timestamp The requested time. On success, this is updated with the time of the selected keyframe. + * @param later If true, the selected frame is the earliest keyframe with a time greater or equal to timestamp. + * If false, the selected frame is the latest keyframe with a time earlier or equal to timestamp. + * @return Status code. + */ +static VC_CONTAINER_STATUS_T rcv_seek_nearest_keyframe(VC_CONTAINER_T *p_ctx, int64_t *timestamp, int later) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + int64_t prev_keyframe_offset = sizeof(RCV_FILE_HEADER_T); /* set to very first frame */ + int64_t prev_keyframe_timestamp = 0; + int use_prev_keyframe = !later; + + if(use_prev_keyframe || (module->frame.timestamp * 1000LL > *timestamp)) + { + /* A seek has been requested to an earlier keyframe, so rewind to the beginning + * of the stream since there's no information available on previous frames */ + SEEK(p_ctx, sizeof(RCV_FILE_HEADER_T)); + memset(&module->frame, 0, sizeof(RCV_FRAME_HEADER_T)); + module->mid_frame = 0; + module->frame_read = 0; + } + + if(module->mid_frame) + { + /* Seek back to the start of the current frame */ + SEEK(p_ctx, STREAM_POSITION(p_ctx) - module->frame_read - sizeof(RCV_FILE_HEADER_T)); + module->mid_frame = 0; + module->frame_read = 0; + } + + while(1) + { + if(PEEK_BYTES(p_ctx, &module->frame, sizeof(RCV_FRAME_HEADER_T)) != sizeof(RCV_FRAME_HEADER_T)) + { + status = VC_CONTAINER_ERROR_EOS; + break; + } + + if(module->frame.keyframe) + { + if(module->index) + vc_container_index_add(module->index, module->frame.timestamp * 1000LL, STREAM_POSITION(p_ctx)); + + if((module->frame.timestamp * 1000LL) >= *timestamp) + { + if((module->frame.timestamp * 1000LL) == *timestamp) + use_prev_keyframe = 0; + + *timestamp = module->frame.timestamp * 1000LL; + + break; + } + + prev_keyframe_offset = STREAM_POSITION(p_ctx); + prev_keyframe_timestamp = module->frame.timestamp * 1000LL; + } + + SKIP_BYTES(p_ctx, module->frame.framesize + sizeof(RCV_FRAME_HEADER_T)); + } + + if(use_prev_keyframe) + { + *timestamp = prev_keyframe_timestamp; + status = SEEK(p_ctx, prev_keyframe_offset); + } + + return status; +} + +/***************************************************************************** +Functions exported as part of the Container Module API +*****************************************************************************/ +static VC_CONTAINER_STATUS_T rcv_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int size; + + if(!module->mid_frame) + { + /* Save the current position for updating the indexer */ + int64_t position = STREAM_POSITION(p_ctx); + + if(READ_BYTES(p_ctx, &module->frame, sizeof(RCV_FRAME_HEADER_T)) != sizeof(RCV_FRAME_HEADER_T)) + return VC_CONTAINER_ERROR_EOS; + module->mid_frame = 1; + module->frame_read = 0; + + if(module->index && module->frame.keyframe) + vc_container_index_add(module->index, (int64_t)module->frame.timestamp * 1000LL, position); + } + + packet->size = module->frame.framesize; + packet->dts = packet->pts = module->frame.timestamp * 1000LL; + packet->track = 0; + packet->flags = 0; + if(module->frame_read == 0) + packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + if(module->frame.keyframe) + packet->flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME; + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + { + size = SKIP_BYTES(p_ctx, module->frame.framesize - module->frame_read); + if((module->frame_read += size) == module->frame.framesize) + { + module->frame_read = 0; + module->mid_frame = 0; + } + return STREAM_STATUS(p_ctx); + } + + if(flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + size = MIN(module->frame.framesize - module->frame_read, packet->buffer_size); + size = READ_BYTES(p_ctx, packet->data, size); + if((module->frame_read += size) == module->frame.framesize) + { + module->frame_read = 0; + module->mid_frame = 0; + packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + } + packet->size = size; + + return size ? VC_CONTAINER_SUCCESS : STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rcv_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + int past = 1; + int64_t position; + int64_t timestamp = *offset; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FAILED; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_PARAM_UNUSED(mode); + + if(module->index) + status = vc_container_index_get(module->index, flags & VC_CONTAINER_SEEK_FLAG_FORWARD, ×tamp, &position, &past); + + if(status == VC_CONTAINER_SUCCESS && !past) + { + /* Indexed keyframe found */ + module->frame_read = 0; + module->mid_frame = 0; + *offset = timestamp; + status = SEEK(p_ctx, position); + } + else + { + /* No indexed keyframe found, so seek through all frames */ + status = rcv_seek_nearest_keyframe(p_ctx, offset, flags & VC_CONTAINER_SEEK_FLAG_FORWARD); + } + + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rcv_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + for(; p_ctx->tracks_num > 0; p_ctx->tracks_num--) + vc_container_free_track(p_ctx, p_ctx->tracks[p_ctx->tracks_num-1]); + + if(module->index) + vc_container_index_free(module->index); + + free(module); + + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T rcv_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + uint8_t dummy[8]; + + /* Quick check for a valid file header */ + if((PEEK_BYTES(p_ctx, dummy, sizeof(dummy)) != sizeof(dummy)) || + dummy[3] != 0xc5 || LI32(dummy+4) != 0x4) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks_num = 1; + p_ctx->tracks = &module->track; + p_ctx->tracks[0] = vc_container_allocate_track(p_ctx, 0); + if(!p_ctx->tracks[0]) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + p_ctx->tracks[0]->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + p_ctx->tracks[0]->format->codec = VC_CONTAINER_CODEC_WMV3; + p_ctx->tracks[0]->is_enabled = true; + + if((status = rcv_read_header(p_ctx)) != VC_CONTAINER_SUCCESS) goto error; + + LOG_DEBUG(p_ctx, "using rcv reader"); + + if(vc_container_index_create(&module->index, 512) == VC_CONTAINER_SUCCESS) + vc_container_index_add(module->index, 0LL, STREAM_POSITION(p_ctx)); + + if(STREAM_SEEKABLE(p_ctx)) + p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + + p_ctx->priv->pf_close = rcv_reader_close; + p_ctx->priv->pf_read = rcv_reader_read; + p_ctx->priv->pf_seek = rcv_reader_seek; + return VC_CONTAINER_SUCCESS; + + error: + if(module) rcv_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function +********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open rcv_reader_open +#endif diff --git a/containers/rtp/CMakeLists.txt b/containers/rtp/CMakeLists.txt new file mode 100644 index 0000000..e6ba3a9 --- /dev/null +++ b/containers/rtp/CMakeLists.txt @@ -0,0 +1,17 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +set(rtp_SRCS ${rtp_SRCS} rtp_reader.c) +set(rtp_SRCS ${rtp_SRCS} rtp_h264.c) +set(rtp_SRCS ${rtp_SRCS} rtp_mpeg4.c) +set(rtp_SRCS ${rtp_SRCS} rtp_base64.c) +add_library(reader_rtp ${LIBRARY_TYPE} ${rtp_SRCS}) + +target_link_libraries(reader_rtp containers) + +install(TARGETS reader_rtp DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/rtp/rtp_base64.c b/containers/rtp/rtp_base64.c new file mode 100644 index 0000000..f0e873f --- /dev/null +++ b/containers/rtp/rtp_base64.c @@ -0,0 +1,165 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "rtp_base64.h" + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +#define LOWEST_BASE64_CHAR '+' +#define HIGHEST_BASE64_CHAR 'z' +#define IN_BASE64_RANGE(C) ((C) >= LOWEST_BASE64_CHAR && (C) <= HIGHEST_BASE64_CHAR) + +/** Used as a marker in the lookup table to indicate an invalid Base64 character */ +#define INVALID 0xFF + +/* Reduced lookup table for translating a character to a 6-bit value. The + * table starts at the lowest Base64 character, '+' */ +uint8_t base64_decode_lookup[] = { + 62, INVALID, 62, INVALID, 63, /* '+' to '/' */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* '0' to '9' */ + INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, /* ':' to '@' */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, /* 'A' to 'T' */ + 20, 21, 22, 23, 24, 25, /* 'U' to 'Z' */ + INVALID, INVALID, INVALID, INVALID, 63, INVALID, /* '[' to '`' */ + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, /* 'a' to 'r' */ + 44, 45, 46, 47, 48, 49, 50, 51 /* 's' to 'z' */ +}; + +/****************************************************************************** +Type definitions +******************************************************************************/ + +/****************************************************************************** +Function prototypes +******************************************************************************/ + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/***************************************************************************** +Functions exported as part of the Base64 API + *****************************************************************************/ + +/*****************************************************************************/ +uint32_t rtp_base64_byte_length(const char *str, uint32_t str_len) +{ + uint32_t character_count = 0; + uint32_t ii; + char cc; + + /* Scan through string until either a pad ('=') character or the end is + * reached. Ignore characters that are not part of the Base64 alphabet. + * Number of bytes should then be 3/4 of the character count. */ + + for (ii = 0; ii < str_len; ii++) + { + cc = *str++; + if (cc == '=') + break; /* Found a pad character: stop */ + + if (!IN_BASE64_RANGE(cc)) + continue; /* Ignore invalid character */ + + if (base64_decode_lookup[cc - LOWEST_BASE64_CHAR] != INVALID) + character_count++; + } + + return (character_count * 3) >> 2; +} + +/*****************************************************************************/ +uint8_t *rtp_base64_decode(const char *str, uint32_t str_len, uint8_t *buffer, uint32_t buffer_len) +{ + uint32_t character_count = 0; + uint32_t value = 0; + uint32_t ii; + char cc; + uint8_t lookup; + + /* Build up sets of four characters (ignoring invalid ones) to generate + * triplets of bytes, until either the end of the string or the pad ('=') + * characters are reached. */ + + for (ii = 0; ii < str_len; ii++) + { + cc = *str++; + if (cc == '=') + break; /* Found a pad character: stop */ + + if (!IN_BASE64_RANGE(cc)) + continue; /* Ignore invalid character */ + + lookup = base64_decode_lookup[cc - LOWEST_BASE64_CHAR]; + if (lookup == INVALID) + continue; /* Ignore invalid character */ + + value = (value << 6) | lookup; + character_count++; + + if (character_count == 4) + { + if (buffer_len < 3) + return NULL; /* Not enough room in the output buffer */ + + *buffer++ = (uint8_t)(value >> 16); + *buffer++ = (uint8_t)(value >> 8); + *buffer++ = (uint8_t)(value ); + buffer_len -= 3; + + character_count = 0; + value = 0; + } + } + + /* If there were extra characters on the end, these need to be handled to get + * the last one or two bytes. */ + + switch (character_count) + { + case 0: /* Nothing more to do, the final bytes were converted in the loop */ + break; + case 2: /* One additional byte, padded with four zero bits */ + if (!buffer_len) + return NULL; + *buffer++ = (uint8_t)(value >> 4); + break; + case 3: /* Two additional bytes, padded with two zero bits */ + if (buffer_len < 2) + return NULL; + *buffer++ = (uint8_t)(value >> 10); + *buffer++ = (uint8_t)(value >> 2); + break; + default: /* This is an invalid Base64 encoding */ + return NULL; + } + + /* Return number of bytes written to the buffer */ + return buffer; +} diff --git a/containers/rtp/rtp_base64.h b/containers/rtp/rtp_base64.h new file mode 100644 index 0000000..2cb6a76 --- /dev/null +++ b/containers/rtp/rtp_base64.h @@ -0,0 +1,49 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _RTP_BASE64_H_ +#define _RTP_BASE64_H_ + +#include "containers/containers.h" + +/** Returns the number of bytes encoded by the given Base64 encoded string. + * + * \param str The Base64 encoded string. + * \param str_len The number of characters in the string. + * \return The number of bytes that can be decoded. */ +uint32_t rtp_base64_byte_length(const char *str, uint32_t str_len); + +/** Decodes a Base64 encoded string into a byte buffer. + * + * \param str The Base64 encoded string. + * \param str_len The number of characters in the string. + * \param buffer The buffer to receive the decoded output. + * \param buffer_len The maximum number of bytes to put in the buffer. + * \return Pointer to byte after the last one converted, or NULL on error. */ +uint8_t *rtp_base64_decode(const char *str, uint32_t str_len, uint8_t *buffer, uint32_t buffer_len); + +#endif /* _RTP_BASE64_H_ */ diff --git a/containers/rtp/rtp_h264.c b/containers/rtp/rtp_h264.c new file mode 100644 index 0000000..e7f0f83 --- /dev/null +++ b/containers/rtp/rtp_h264.c @@ -0,0 +1,803 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include + +#include "containers/containers.h" + +#include "containers/core/containers_logging.h" +#include "containers/core/containers_list.h" +#include "containers/core/containers_bits.h" +#include "rtp_priv.h" +#include "rtp_base64.h" +#include "rtp_h264.h" + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +/** H.264 payload flag bits */ +typedef enum +{ + H264F_NEXT_PACKET_IS_START = 0, + H264F_INSIDE_FRAGMENT, + H264F_OUTPUT_NAL_HEADER, +} h264_flag_bit_t; + +/** Bit mask to extract F zero bit from NAL unit header */ +#define NAL_UNIT_FZERO_MASK 0x80 +/** Bit mask to extract NAL unit type from NAL unit header */ +#define NAL_UNIT_TYPE_MASK 0x1F + +/** NAL unit type codes */ +enum +{ + /* 0 unspecified */ + NAL_UNIT_NON_IDR = 1, + NAL_UNIT_PARTITION_A = 2, + NAL_UNIT_PARTITION_B = 3, + NAL_UNIT_PARTITION_C = 4, + NAL_UNIT_IDR = 5, + NAL_UNIT_SEI = 6, + NAL_UNIT_SEQUENCE_PARAMETER_SET = 7, + NAL_UNIT_PICTURE_PARAMETER_SET = 8, + NAL_UNIT_ACCESS_UNIT_DELIMITER = 9, + NAL_UNIT_END_OF_SEQUENCE = 10, + NAL_UNIT_END_OF_STREAM = 11, + NAL_UNIT_FILLER = 12, + NAL_UNIT_EXT_SEQUENCE_PARAMETER_SET = 13, + NAL_UNIT_PREFIX = 14, + NAL_UNIT_SUBSET_SEQUENCE_PARAMETER_SET = 15, + /* 16 to 18 reserved */ + NAL_UNIT_AUXILIARY = 19, + NAL_UNIT_EXTENSION = 20, + /* 21 to 23 reserved */ + NAL_UNIT_STAP_A = 24, + NAL_UNIT_STAP_B = 25, + NAL_UNIT_MTAP16 = 26, + NAL_UNIT_MTAP24 = 27, + NAL_UNIT_FU_A = 28, + NAL_UNIT_FU_B = 29, + /* 30 to 31 unspecified */ +}; + +/** Fragment unit header indicator bits */ +typedef enum +{ + FRAGMENT_UNIT_HEADER_RESERVED = 5, + FRAGMENT_UNIT_HEADER_END = 6, + FRAGMENT_UNIT_HEADER_START = 7, +} fragment_unit_header_bit_t; + +#define MACROBLOCK_WIDTH 16 +#define MACROBLOCK_HEIGHT 16 + +/** H.264 RTP timestamp clock rate */ +#define H264_TIMESTAMP_CLOCK 90000 + +typedef enum +{ + CHROMA_FORMAT_MONO = 0, + CHROMA_FORMAT_YUV_420 = 1, + CHROMA_FORMAT_YUV_422 = 2, + CHROMA_FORMAT_YUV_444 = 3, + CHROMA_FORMAT_YUV_444_PLANAR = 4, + CHROMA_FORMAT_RGB = 5, +} CHROMA_FORMAT_T; + +uint32_t chroma_sub_width[] = { + 1, 2, 2, 1, 1, 1 +}; + +uint32_t chroma_sub_height[] = { + 1, 2, 1, 1, 1, 1 +}; + +/****************************************************************************** +Type definitions +******************************************************************************/ + +typedef struct h264_payload_tag +{ + uint32_t nal_unit_size; /**< Number of NAL unit bytes left to write */ + uint8_t flags; /**< H.264 payload flags */ + uint8_t header_bytes_to_write; /**< Number of start code bytes left to write */ + uint8_t nal_header; /**< Header for next NAL unit */ +} H264_PAYLOAD_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T h264_parameter_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/**************************************************************************//** + * Remove emulation prevention bytes from a buffer. + * These are 0x03 bytes inserted to prevent misinterprentation of a byte + * sequence in a buffer as a start code. + * + * @param sprop The buffer from which bytes are to be removed. + * @param sprop_size The number of bytes in the buffer. + * @return The new number of bytes in the buffer. + */ +static uint32_t h264_remove_emulation_prevention_bytes(uint8_t *sprop, + uint32_t sprop_size) +{ + uint32_t offset = 0; + uint8_t nal_unit_type = sprop[offset++]; + uint32_t new_sprop_size = sprop_size; + uint8_t first_byte, second_byte; + + nal_unit_type &= 0x1F; /* Just keep NAL unit type bits */ + + /* Certain NAL unit types need a byte triplet passed first */ + if (nal_unit_type == NAL_UNIT_PREFIX || nal_unit_type == NAL_UNIT_EXTENSION) + offset += 3; + + /* Make sure there is enough data for there to be a 0x00 0x00 0x03 sequence */ + if (offset + 2 >= new_sprop_size) + return new_sprop_size; + + /* Keep a rolling set of the last couple of bytes */ + first_byte = sprop[offset++]; + second_byte = sprop[offset++]; + + while (offset < new_sprop_size) + { + uint8_t next_byte = sprop[offset]; + + if (!first_byte && !second_byte && next_byte == 0x03) + { + /* Remove the emulation prevention byte (0x03) */ + new_sprop_size--; + if (offset == new_sprop_size) /* No more data to check */ + break; + memmove(&sprop[offset], &sprop[offset + 1], new_sprop_size - offset); + next_byte = sprop[offset]; + } else + offset++; + + first_byte = second_byte; + second_byte = next_byte; + } + + return new_sprop_size; +} + +/**************************************************************************//** + * Skip a scaling list in a bit stream. + * + * @param p_ctx The container context. + * @param sprop The bit stream containing the scaling list. + * @param size_of_scaling_list The size of the scaling list. + */ +static void h264_skip_scaling_list(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_BITS_T *sprop, + uint32_t size_of_scaling_list) +{ + uint32_t last_scale = 8; + uint32_t next_scale = 8; + int32_t delta_scale; + uint32_t jj; + + /* Algorithm taken from H.264 section 7.3.2.1.1.1 */ + for (jj = 0; jj < size_of_scaling_list; jj++) + { + if (next_scale) + { + delta_scale = BITS_READ_S32_EXP(p_ctx, sprop, "delta_scale"); + next_scale = (last_scale + delta_scale + 256) & 0xFF; + + if (next_scale) + last_scale = next_scale; + } + } +} + +/**************************************************************************//** + * Get the chroma format from the bit stream. + * + * @param p_ctx The container context. + * @param sprop The bit stream containing the scaling list. + * @return The chroma format index. + */ +static uint32_t h264_get_chroma_format(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_BITS_T *sprop) +{ + uint32_t chroma_format_idc; + + chroma_format_idc = BITS_READ_U32_EXP(p_ctx, sprop, "chroma_format_idc"); + if (chroma_format_idc == 3 && BITS_READ_U32(p_ctx, sprop, 1, "separate_colour_plane_flag")) + chroma_format_idc = CHROMA_FORMAT_YUV_444_PLANAR; + + BITS_SKIP_EXP(p_ctx, sprop, "bit_depth_luma_minus8"); + BITS_SKIP_EXP(p_ctx, sprop, "bit_depth_chroma_minus8"); + BITS_SKIP(p_ctx, sprop, 1, "qpprime_y_zero_transform_bypass_flag"); + + if (BITS_READ_U32(p_ctx, sprop, 1, "seq_scaling_matrix_present_flag")) + { + uint32_t scaling_lists = (chroma_format_idc == 3) ? 12 : 8; + uint32_t ii; + + for (ii = 0; ii < scaling_lists; ii++) + { + if (BITS_READ_U32(p_ctx, sprop, 1, "seq_scaling_list_present_flag")) + h264_skip_scaling_list(p_ctx, sprop, (ii < 6) ? 16 : 64); + } + } + + return chroma_format_idc; +} + +/**************************************************************************//** + * Decode an H.264 sequence parameter set and update track information. + * + * @param p_ctx The RTP container context. + * @param track The track to be updated. + * @param sprop The bit stream containing the sequence parameter set. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T h264_decode_sequence_parameter_set(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_BITS_T *sprop) +{ + VC_CONTAINER_VIDEO_FORMAT_T *video = &track->format->type->video; + uint32_t pic_order_cnt_type, chroma_format_idc; + uint32_t pic_width_in_mbs_minus1, pic_height_in_map_units_minus1, frame_mbs_only_flag; + uint32_t frame_crop_left_offset, frame_crop_right_offset, frame_crop_top_offset, frame_crop_bottom_offset; + uint8_t profile_idc; + + /* This structure is defined by H.264 section 7.3.2.1.1 */ + profile_idc = BITS_READ_U8(p_ctx, sprop, 8, "profile_idc"); + BITS_SKIP(p_ctx, sprop, 16, "Rest of profile_level_id"); + + BITS_READ_U32_EXP(p_ctx, sprop, "seq_parameter_set_id"); + + chroma_format_idc = CHROMA_FORMAT_RGB; + if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || + profile_idc == 244 || profile_idc == 44 || profile_idc == 83 || + profile_idc == 86 || profile_idc == 118 || profile_idc == 128) + { + chroma_format_idc = h264_get_chroma_format(p_ctx, sprop); + if (chroma_format_idc > CHROMA_FORMAT_YUV_444_PLANAR) + goto error; + } + + BITS_SKIP_EXP(p_ctx, sprop, "log2_max_frame_num_minus4"); + pic_order_cnt_type = BITS_READ_U32_EXP(p_ctx, sprop, "pic_order_cnt_type"); + if (pic_order_cnt_type == 0) + { + BITS_SKIP_EXP(p_ctx, sprop, "log2_max_pic_order_cnt_lsb_minus4"); + } + else if (pic_order_cnt_type == 1) + { + uint32_t num_ref_frames_in_pic_order_cnt_cycle; + uint32_t ii; + + BITS_SKIP(p_ctx, sprop, 1, "delta_pic_order_always_zero_flag"); + BITS_SKIP_EXP(p_ctx, sprop, "offset_for_non_ref_pic"); + BITS_SKIP_EXP(p_ctx, sprop, "offset_for_top_to_bottom_field"); + num_ref_frames_in_pic_order_cnt_cycle = BITS_READ_U32_EXP(p_ctx, sprop, "num_ref_frames_in_pic_order_cnt_cycle"); + + for (ii = 0; ii < num_ref_frames_in_pic_order_cnt_cycle; ii++) + BITS_SKIP_EXP(p_ctx, sprop, "offset_for_ref_frame"); + } + + BITS_SKIP_EXP(p_ctx, sprop, "max_num_ref_frames"); + BITS_SKIP(p_ctx, sprop, 1, "gaps_in_frame_num_value_allowed_flag"); + + pic_width_in_mbs_minus1 = BITS_READ_U32_EXP(p_ctx, sprop, "pic_width_in_mbs_minus1"); + pic_height_in_map_units_minus1 = BITS_READ_U32_EXP(p_ctx, sprop, "pic_height_in_map_units_minus1"); + frame_mbs_only_flag = BITS_READ_U32(p_ctx, sprop, 1, "frame_mbs_only_flag"); + + /* Can now set the overall width and height in pixels */ + video->width = (pic_width_in_mbs_minus1 + 1) * MACROBLOCK_WIDTH; + video->height = (2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * MACROBLOCK_HEIGHT; + + if (!frame_mbs_only_flag) + BITS_SKIP(p_ctx, sprop, 1, "mb_adaptive_frame_field_flag"); + BITS_SKIP(p_ctx, sprop, 1, "direct_8x8_inference_flag"); + + if (BITS_READ_U32(p_ctx, sprop, 1, "frame_cropping_flag")) + { + /* Visible area is restricted */ + frame_crop_left_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_left_offset"); + frame_crop_right_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_right_offset"); + frame_crop_top_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_top_offset"); + frame_crop_bottom_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_bottom_offset"); + + /* Need to adjust offsets for 4:2:0 and 4:2:2 chroma formats and field/frame flag */ + frame_crop_left_offset *= chroma_sub_width[chroma_format_idc]; + frame_crop_right_offset *= chroma_sub_width[chroma_format_idc]; + frame_crop_top_offset *= chroma_sub_height[chroma_format_idc] * (2 - frame_mbs_only_flag); + frame_crop_bottom_offset *= chroma_sub_height[chroma_format_idc] * (2 - frame_mbs_only_flag); + + if ((frame_crop_left_offset + frame_crop_right_offset) >= video->width || + (frame_crop_top_offset + frame_crop_bottom_offset) >= video->height) + { + LOG_ERROR(p_ctx, "H.264: frame crop offsets (%u, %u, %u, %u) larger than frame (%u, %u)", + frame_crop_left_offset, frame_crop_right_offset, frame_crop_top_offset, + frame_crop_bottom_offset, video->width, video->height); + goto error; + } + + video->x_offset = frame_crop_left_offset; + video->y_offset = frame_crop_top_offset; + video->visible_width = video->width - frame_crop_left_offset - frame_crop_right_offset; + video->visible_height = video->height - frame_crop_top_offset - frame_crop_bottom_offset; + } else { + video->visible_width = video->width; + video->visible_height = video->height; + } + + /* vui_parameters may follow, but these will not be decoded */ + + if (!BITS_VALID(p_ctx, sprop)) + goto error; + + return VC_CONTAINER_SUCCESS; + +error: + LOG_ERROR(p_ctx, "H.264: sequence_parameter_set failed to decode"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; +} + +/**************************************************************************//** + * Decode an H.264 sprop and update track information. + * + * @param p_ctx The RTP container context. + * @param track The track to be updated. + * @param sprop The bit stream containing the sprop. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T h264_decode_sprop(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_BITS_T *sprop) +{ + switch (BITS_READ_U32(p_ctx, sprop, 8, "nal_unit_header") & NAL_UNIT_TYPE_MASK) + { + case NAL_UNIT_SEQUENCE_PARAMETER_SET: + return h264_decode_sequence_parameter_set(p_ctx, track, sprop); + case NAL_UNIT_PICTURE_PARAMETER_SET: + /* Not handled, but valid */ + return VC_CONTAINER_SUCCESS; + default: + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } +} + +/**************************************************************************//** + * Decode the sprop parameter sets URI parameter and update track information. + * + * @param p_ctx The RTP container context. + * @param track The track to be updated. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T h264_get_sprop_parameter_sets(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + VC_CONTAINER_STATUS_T status; + PARAMETER_T param; + size_t str_len; + uint32_t extradata_size = 0; + uint8_t *sprop; + const char *set; + const char *comma; + + /* Get the value of sprop-parameter-sets, base64 decode the (comma separated) + * sets, store all of them in track->priv->extradata and also decode to + * validate and fill in video format info. */ + + param.name = "sprop-parameter-sets"; + if (!vc_containers_list_find_entry(params, ¶m) || !param.value) + { + LOG_ERROR(p_ctx, "H.264: sprop-parameter-sets is required, but not found"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + /* First pass, calculate total size of buffer needed */ + set = param.value; + do { + comma = strchr(set, ','); + str_len = comma ? (size_t)(comma - set) : strlen(set); + /* Allow space for the NAL unit and a start code */ + extradata_size += rtp_base64_byte_length(set, str_len) + 4; + set = comma + 1; + } while (comma); + + if (!extradata_size) + { + LOG_ERROR(p_ctx, "H.264: sprop-parameter-sets doesn't contain useful data"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + status = vc_container_track_allocate_extradata(p_ctx, track, extradata_size); + if(status != VC_CONTAINER_SUCCESS) return status; + + track->format->extradata_size = extradata_size; + sprop = track->priv->extradata; + + /* Now decode the data into the buffer, and validate / use it to fill in format */ + set = param.value; + do { + uint8_t *next_sprop; + uint32_t sprop_size; + VC_CONTAINER_BITS_T sprop_stream; + + comma = strchr(set, ','); + str_len = comma ? (size_t)(comma - set) : strlen(set); + + /* Insert a start code (0x00000001 in network order) */ + *sprop++ = 0x00; *sprop++ = 0x00; *sprop++ = 0x00; *sprop++ = 0x01; + extradata_size -= 4; + + next_sprop = rtp_base64_decode(set, str_len, sprop, extradata_size); + if (!next_sprop) + { + LOG_ERROR(p_ctx, "H.264: sprop-parameter-sets failed to decode"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + sprop_size = next_sprop - sprop; + if (sprop_size) + { + uint32_t new_sprop_size; + + /* Need to remove emulation prevention bytes before decoding */ + new_sprop_size = h264_remove_emulation_prevention_bytes(sprop, sprop_size); + + BITS_INIT(p_ctx, &sprop_stream, sprop, new_sprop_size); + status = h264_decode_sprop(p_ctx, track, &sprop_stream); + if(status != VC_CONTAINER_SUCCESS) return status; + + /* If necessary, decode sprop again, to put back the emulation prevention bytes */ + if (new_sprop_size != sprop_size) + rtp_base64_decode(set, str_len, sprop, sprop_size); + + extradata_size -= sprop_size; + sprop = next_sprop; + } + + set = comma + 1; + } while (comma); + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Check URI parameter list for unsupported features. + * + * @param p_ctx The RTP container context. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T h264_check_unsupported_features(VC_CONTAINER_T *p_ctx, + const VC_CONTAINERS_LIST_T *params) +{ + uint32_t u32_unused; + + /* Limitation: interleaving not yet supported */ + if (rtp_get_parameter_u32(params, "sprop-interleaving-depth", &u32_unused) || + rtp_get_parameter_u32(params, "sprop-deint-buf-req", &u32_unused) || + rtp_get_parameter_u32(params, "sprop-init-buf-time", &u32_unused) || + rtp_get_parameter_u32(params, "sprop-max-don-diff", &u32_unused)) + { + LOG_ERROR(p_ctx, "H.264: Interleaved packetization is not supported"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Get and check the packetization mode URI parameter. + * + * @param p_ctx The RTP container context. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T h264_get_packetization_mode(VC_CONTAINER_T *p_ctx, + const VC_CONTAINERS_LIST_T *params) +{ + uint32_t packetization_mode; + + if (rtp_get_parameter_u32(params, "packetization-mode", &packetization_mode)) + { + /* Only modes 0 and 1 are supported, no interleaving */ + if (packetization_mode > 1) + { + LOG_ERROR(p_ctx, "H.264: Unsupported packetization mode: %u", packetization_mode); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Initialise payload bit stream for a new RTP packet. + * + * @param p_ctx The RTP container context. + * @param t_module The track module with the new RTP packet. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T h264_new_rtp_packet(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module) +{ + VC_CONTAINER_BITS_T *payload = &t_module->payload; + H264_PAYLOAD_T *extra = (H264_PAYLOAD_T *)t_module->extra; + uint8_t unit_header; + uint8_t fragment_header; + + /* Read the NAL unit type and process as necessary */ + unit_header = BITS_READ_U8(p_ctx, payload, 8, "nal_unit_header"); + + /* When the top bit is set, the NAL unit is invalid */ + if (unit_header & NAL_UNIT_FZERO_MASK) + { + LOG_DEBUG(p_ctx, "H.264: Invalid NAL unit (top bit of header set)"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + /* In most cases, a new packet means a new NAL unit, which will need a start code and the header */ + extra->header_bytes_to_write = 5; + extra->nal_header = unit_header; + extra->nal_unit_size = BITS_BYTES_AVAILABLE(p_ctx, payload); + + switch (unit_header & NAL_UNIT_TYPE_MASK) + { + case NAL_UNIT_STAP_A: + /* Single Time Aggregation Packet A */ + CLEAR_BIT(extra->flags, H264F_INSIDE_FRAGMENT); + /* Trigger reading NAL unit length and header */ + extra->nal_unit_size = 0; + break; + + case NAL_UNIT_FU_A: + /* Fragementation Unit A */ + fragment_header = BITS_READ_U8(p_ctx, payload, 8, "fragment_header"); + extra->nal_unit_size--; + + if (BIT_IS_CLEAR(fragment_header, FRAGMENT_UNIT_HEADER_START) || + BIT_IS_SET(extra->flags, H264F_INSIDE_FRAGMENT)) + { + /* This is a continuation packet, prevent start code and header from being output */ + extra->header_bytes_to_write = 0; + + /* If this is the end of a fragment, the next FU will be a new one */ + if (BIT_IS_SET(fragment_header, FRAGMENT_UNIT_HEADER_END)) + CLEAR_BIT(extra->flags, H264F_INSIDE_FRAGMENT); + } else { + /* Start of a new fragment. */ + SET_BIT(extra->flags, H264F_INSIDE_FRAGMENT); + + /* Merge type from fragment header and the rest from NAL unit header to form real NAL unit header */ + fragment_header &= NAL_UNIT_TYPE_MASK; + fragment_header |= (unit_header & ~NAL_UNIT_TYPE_MASK); + extra->nal_header = fragment_header; + } + break; + + case NAL_UNIT_STAP_B: + case NAL_UNIT_MTAP16: + case NAL_UNIT_MTAP24: + case NAL_UNIT_FU_B: + LOG_ERROR(p_ctx, "H.264: Unsupported RTP NAL unit type: %u", unit_header & NAL_UNIT_TYPE_MASK); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + default: + /* Single NAL unit case */ + CLEAR_BIT(extra->flags, H264F_INSIDE_FRAGMENT); + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * H.264 payload handler. + * Extracts/skips data from the payload according to the NAL unit headers. + * + * @param p_ctx The RTP container context. + * @param track The track being read. + * @param p_packet The container packet information, or NULL. + * @param flags The container read flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T h264_payload_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_PACKET_T *p_packet, + uint32_t flags) +{ + VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; + VC_CONTAINER_BITS_T *payload = &t_module->payload; + H264_PAYLOAD_T *extra = (H264_PAYLOAD_T *)t_module->extra; + uint32_t packet_flags = 0; + uint8_t header_bytes_to_write; + uint32_t size, offset; + uint8_t *data_ptr; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + bool last_nal_unit_in_packet = false; + + if (BIT_IS_SET(t_module->flags, TRACK_NEW_PACKET)) + { + status = h264_new_rtp_packet(p_ctx, t_module); + if (status != VC_CONTAINER_SUCCESS) + return status; + } + + if (BIT_IS_SET(extra->flags, H264F_NEXT_PACKET_IS_START)) + { + packet_flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + + if (!(flags & VC_CONTAINER_READ_FLAG_INFO)) + CLEAR_BIT(extra->flags, H264F_NEXT_PACKET_IS_START); + } + + if (!extra->nal_unit_size && BITS_BYTES_AVAILABLE(p_ctx, payload)) + { + uint32_t stap_unit_header; + + /* STAP-A packet: read NAL unit size and header from payload */ + stap_unit_header = BITS_READ_U32(p_ctx, payload, 24, "STAP unit header"); + extra->nal_unit_size = stap_unit_header >> 8; + if (extra->nal_unit_size > BITS_BYTES_AVAILABLE(p_ctx, payload)) + { + LOG_ERROR(p_ctx, "H.264: STAP-A NAL unit size bigger than payload"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + extra->header_bytes_to_write = 5; + extra->nal_header = (uint8_t)stap_unit_header; + } + + header_bytes_to_write = extra->header_bytes_to_write; + size = extra->nal_unit_size + header_bytes_to_write; + + if (p_packet && !(flags & VC_CONTAINER_READ_FLAG_SKIP)) + { + if (flags & VC_CONTAINER_READ_FLAG_INFO) + { + /* In order to set the frame end flag correctly, need to work out if this + * is the only NAL unit or last in an aggregated packet */ + last_nal_unit_in_packet = (extra->nal_unit_size == BITS_BYTES_AVAILABLE(p_ctx, payload)); + } else { + offset = 0; + data_ptr = p_packet->data; + + if (size > p_packet->buffer_size) + { + /* Buffer not big enough */ + size = p_packet->buffer_size; + } + + /* Insert start code and header into the data stream */ + while (offset < size && header_bytes_to_write) + { + uint8_t header_byte; + + switch (header_bytes_to_write) + { + case 2: header_byte = 0x01; break; + case 1: header_byte = extra->nal_header; break; + default: header_byte = 0x00; + } + data_ptr[offset++] = header_byte; + header_bytes_to_write--; + } + extra->header_bytes_to_write = header_bytes_to_write; + + if (offset < size) + { + BITS_COPY_BYTES(p_ctx, payload, size - offset, data_ptr + offset, "Packet data"); + extra->nal_unit_size -= (size - offset); + } + + /* If we've read the final bytes of the packet, this must be the last (or only) + * NAL unit in it */ + last_nal_unit_in_packet = !BITS_BYTES_AVAILABLE(p_ctx, payload); + } + p_packet->size = size; + } else { + extra->header_bytes_to_write = 0; + BITS_SKIP_BYTES(p_ctx, payload, extra->nal_unit_size, "Packet data"); + last_nal_unit_in_packet = !BITS_BYTES_AVAILABLE(p_ctx, payload); + extra->nal_unit_size = 0; + } + + /* The marker bit on an RTP packet indicates the frame ends at the end of packet */ + if (last_nal_unit_in_packet && BIT_IS_SET(t_module->flags, TRACK_HAS_MARKER)) + { + packet_flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + + /* If this was the last packet of a frame, the next one must be the start */ + if (!(flags & VC_CONTAINER_READ_FLAG_INFO)) + SET_BIT(extra->flags, H264F_NEXT_PACKET_IS_START); + } + + if (p_packet) + p_packet->flags = packet_flags; + + return status; +} + +/***************************************************************************** +Functions exported as part of the RTP parameter handler API + *****************************************************************************/ + +/**************************************************************************//** + * H.264 parameter handler. + * Parses the URI parameters to set up the track for an H.264 stream. + * + * @param p_ctx The reader context. + * @param track The track to be updated. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +VC_CONTAINER_STATUS_T h264_parameter_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + H264_PAYLOAD_T *extra; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(params); + + /* See RFC3984, section 8.1, for parameter names and details. */ + extra = (H264_PAYLOAD_T *)malloc(sizeof(H264_PAYLOAD_T)); + if (!extra) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + track->priv->module->extra = extra; + memset(extra, 0, sizeof(H264_PAYLOAD_T)); + + /* Mandatory parameters */ + status = h264_get_sprop_parameter_sets(p_ctx, track, params); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* Unsupported parameters */ + status = h264_check_unsupported_features(p_ctx, params); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* Optional parameters */ + status = h264_get_packetization_mode(p_ctx, params); + if (status != VC_CONTAINER_SUCCESS) return status; + + track->priv->module->payload_handler = h264_payload_handler; + SET_BIT(extra->flags, H264F_NEXT_PACKET_IS_START); + + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + track->priv->module->timestamp_clock = H264_TIMESTAMP_CLOCK; + + return status; +} + diff --git a/containers/rtp/rtp_h264.h b/containers/rtp/rtp_h264.h new file mode 100644 index 0000000..ffd51da --- /dev/null +++ b/containers/rtp/rtp_h264.h @@ -0,0 +1,42 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _RTP_H264_H_ +#define _RTP_H264_H_ + +#include "containers/containers.h" +#include "containers/core/containers_list.h" + +/** H.264 parameter handler + * + * \param p_ctx Container context. + * \param track Track data. + * \param params Parameter list. + * \return Status of decoding the H.264 parameters. */ +VC_CONTAINER_STATUS_T h264_parameter_handler(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); + +#endif /* _RTP_H264_H_ */ diff --git a/containers/rtp/rtp_mpeg4.c b/containers/rtp/rtp_mpeg4.c new file mode 100644 index 0000000..6a968e4 --- /dev/null +++ b/containers/rtp/rtp_mpeg4.c @@ -0,0 +1,788 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include + +#include "containers/containers.h" + +#include "containers/core/containers_logging.h" +#include "containers/core/containers_bits.h" +#include "containers/core/containers_list.h" +#include "rtp_priv.h" +#include "rtp_mpeg4.h" + +#ifdef _DEBUG +#define RTP_DEBUG 1 +#endif + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +/****************************************************************************** +Type definitions +******************************************************************************/ + +/** MPEG-4 stream types, ISO/IEC 14496-1:2010 Table 6 */ +typedef enum +{ + MPEG4_OBJECT_DESCRIPTOR_STREAM = 1, + MPEG4_CLOCK_REFERENCE_STREAM = 2, + MPEG4_SCENE_DESCRIPTION_STREAM = 3, + MPEG4_VISUAL_STREAM = 4, + MPEG4_AUDIO_STREAM = 5, + MPEG4_MPEG7_STREAM = 6, + MPEG4_IPMP_STREAM = 7, + MPEG4_OBJECT_CONTENT_INFO_STREAM = 8, + MPEG4_MPEGJ_STREAM = 9, + MPEG4_INTERACTION_STREAM = 10, + MPEG4_IPMP_TOOL_STREAM = 11, +} mp4_stream_type_t; + +/** MPEG-4 audio object types, ISO/IEC 14496-3:2009 Table 1.17 */ +typedef enum +{ + MPEG4A_AAC_MAIN = 1, + MPEG4A_AAC_LC = 2, + MPEG4A_AAC_SSR = 3, + MPEG4A_AAC_LTP = 4, + MPEG4A_SBR = 5, + MPEG4A_AAC_SCALABLE = 6, + MPEG4A_TWIN_VQ = 7, + MPEG4A_CELP = 8, + MPEG4A_HVXC = 9, + MPEG4A_TTSI = 12, + MPEG4A_MAIN_SYNTHETIC = 13, + MPEG4A_WAVETABLE_SYNTHESIS = 14, + MPEG4A_GENERAL_MIDI = 15, + MPEG4A_ALGORITHMIC_SYNTHESIS = 16, + MPEG4A_ER_AAC_LC = 17, + MPEG4A_ER_AAC_LTP = 19, + MPEG4A_ER_AAC_SCALABLE = 20, + MPEG4A_ER_TWIN_VQ = 21, + MPEG4A_ER_BSAC = 22, + MPEG4A_ER_AAC_LD = 23, + MPEG4A_ER_CELP = 24, + MPEG4A_ER_HVXC = 25, + MPEG4A_ER_HILN = 26, + MPEG4A_ER_PARAMETERIC = 27, + MPEG4A_SSC = 28, + MPEG4A_PS = 29, + MPEG4A_MPEG_SURROUND = 30, + MPEG4A_LAYER_1 = 32, + MPEG4A_LAYER_2 = 33, + MPEG4A_LAYER_3 = 34, + MPEG4A_DST = 35, + MPEG4A_ALS = 36, + MPEG4A_SLS = 37, + MPEG4A_SLS_NON_CORE = 38, + MPEG4A_ER_AAC_ELD = 39, + MPEG4A_SMR_SIMPLE = 40, + MPEG4A_SMR_MAIN = 41, +} mp4_audio_object_type_t; + +/** RTP MPEG-4 modes */ +typedef enum +{ + MP4_GENERIC_MODE = 0, + MP4_CELP_CBR_MODE, + MP4_CELP_VBR_MODE, + MP4_AAC_LBR_MODE, + MP4_AAC_HBR_MODE +} mp4_mode_t; + +typedef struct mp4_mode_detail_tag +{ + const char *name; + mp4_mode_t mode; +} MP4_MODE_ENTRY_T; + +/* RTP MPEG-4 mode look-up table. + * Note: case-insensitive sort by name */ +static MP4_MODE_ENTRY_T mp4_mode_array[] = { + { "aac-hbr", MP4_AAC_HBR_MODE }, + { "aac-lbr", MP4_AAC_LBR_MODE }, + { "celp-cbr", MP4_CELP_CBR_MODE }, + { "celp-vbr", MP4_CELP_VBR_MODE }, + { "generic", MP4_GENERIC_MODE }, +}; + +static int mp4_mode_comparator(const MP4_MODE_ENTRY_T *a, const MP4_MODE_ENTRY_T *b); + +VC_CONTAINERS_STATIC_LIST(mp4_mode_lookup, mp4_mode_array, mp4_mode_comparator); + +typedef struct au_info_tag +{ + uint32_t available; + uint32_t index; + int32_t cts_delta; + int32_t dts_delta; +} AU_INFO_T; + +typedef struct mp4_payload_tag +{ + mp4_stream_type_t stream_type; + uint32_t profile_level_id; + mp4_mode_t mode; + uint32_t size_length; + uint32_t index_length; + uint32_t index_delta_length; + uint32_t cts_delta_length; + uint32_t dts_delta_length; + uint32_t object_type; + uint32_t constant_size; + uint32_t constant_duration; + uint32_t auxiliary_length; + VC_CONTAINER_BITS_T au_headers; + AU_INFO_T au_info; +} MP4_PAYLOAD_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T mp4_parameter_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/**************************************************************************//** + * Convert a hexadecimal character to a value between 0 and 15. + * Upper and lower case characters are supported. An invalid chacter return zero. + * + * @param hex The character to convert. + * @return The value of the character. + */ +static uint8_t hex_to_nybble(char hex) +{ + if (hex >= '0' && hex <= '9') + return hex - '0'; + if (hex >= 'A' && hex <= 'F') + return hex - 'A' + 10; + if (hex >= 'a' && hex <= 'f') + return hex - 'a' + 10; + return 0; /* Illegal character (not hex) */ +} + +/**************************************************************************//** + * Convert a sequence of hexadecimal characters to consecutive entries in a + * byte array. + * The string must contain at least twice as many characters as the number of + * bytes to convert. + * + * @param hex The hexadecimal string. + * @param buffer The buffer into which bytes are to be stored. + * @param bytes_to_convert The number of bytes in the array to be filled. + */ +static void hex_to_byte_buffer(const char *hex, + uint8_t *buffer, + uint32_t bytes_to_convert) +{ + uint8_t value; + + while (bytes_to_convert--) + { + value = hex_to_nybble(*hex++) << 4; + value |= hex_to_nybble(*hex++); + *buffer++ = value; + } +} + +/**************************************************************************//** + * Retrieves and checks the stream type in the URI parameters. + * + * @param p_ctx The RTP container context. + * @param track The track being constructed. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_get_stream_type(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + MP4_PAYLOAD_T *extra = (MP4_PAYLOAD_T *)track->priv->module->extra; + uint32_t stream_type; + VC_CONTAINER_ES_TYPE_T expected_es_type; + + if (!rtp_get_parameter_u32(params, "streamType", &stream_type)) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + switch (stream_type) + { + case MPEG4_AUDIO_STREAM: + extra->stream_type = MPEG4_AUDIO_STREAM; + expected_es_type = VC_CONTAINER_ES_TYPE_AUDIO; + break; + default: + LOG_ERROR(p_ctx, "Unsupported MPEG-4 stream type: %u", stream_type); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + if (track->format->es_type != expected_es_type) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Decode and store audio configuration information from an MP4 audio + * configuration bit stream. + * + * @param p_ctx The RTP container context. + * @param track The track being constructed. + * @param bit_stream The bit stream containing the audio configuration. + * @return True if the configuration was decoded successfully, false otherwise. + */ +static bool mp4_decode_audio_config(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_BITS_T *bit_stream) +{ + static uint32_t mp4_audio_sample_rate[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, + 22050, 16000, 12000, 11025, 8000, 7350, 0, 0 }; + + VC_CONTAINER_AUDIO_FORMAT_T *audio = &track->format->type->audio; + uint32_t audio_object_type; + uint32_t sampling_frequency_index; + uint32_t channel_configuration; + + audio_object_type = BITS_READ_U32(p_ctx, bit_stream, 5, "audioObjectType"); + if (audio_object_type == 31) + audio_object_type = BITS_READ_U32(p_ctx, bit_stream, 6, "audioObjectTypeExt") + 32; + + sampling_frequency_index = BITS_READ_U32(p_ctx, bit_stream, 4, "samplingFrequencyIndex"); + if (sampling_frequency_index == 0xF) + audio->sample_rate = BITS_READ_U32(p_ctx, bit_stream, 24, "samplingFrequency"); + else + audio->sample_rate = mp4_audio_sample_rate[sampling_frequency_index]; + if (!audio->sample_rate) return false; + + track->priv->module->timestamp_clock = audio->sample_rate; + + channel_configuration = BITS_READ_U32(p_ctx, bit_stream, 4, "channelConfiguration"); + switch (channel_configuration) + { + case 1: /* 1 channel, centre front */ + case 2: /* 2 channel, stereo front */ + case 3: /* 3 channel, centre and stereo front */ + case 4: /* 4 channel, centre and stereo front, mono surround */ + case 5: /* 5 channel, centre and stereo front, stereo surround */ + case 6: /* 5.1 channel, centre and stereo front, stereo surround, low freq */ + audio->channels = channel_configuration; + break; + case 7: /* 7.1 channel, centre, stereo and stereo outside front, stereo surround, low freq */ + audio->channels = channel_configuration + 1; + break; + default: + LOG_DEBUG(p_ctx, "MPEG-4: Unsupported channel configuration (%u)", channel_configuration); + return false; + } + + switch (audio_object_type) + { + case MPEG4A_AAC_LC: + { + uint32_t ga_specific_config = BITS_READ_U32(p_ctx, bit_stream, 3, "GASpecificConfig"); + + /* Make sure there are no unexpected (and unsupported) additional configuration elements */ + if (ga_specific_config != 0) + { + LOG_DEBUG(p_ctx, "MPEG-4: Unexpected additional configuration data (%u)", ga_specific_config); + return false; + } + } + break; + /* Add any further supported codecs here */ + default: + LOG_DEBUG(p_ctx, "MPEG-4: Unsupported Audio Object Type (%u)", audio_object_type); + return false; + } + + return true; +} + +/**************************************************************************//** + * Get, store and decode the configuration information from the URI parameters. + * + * @param p_ctx The RTP container context. + * @param track The track being constructed. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_get_config(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + MP4_PAYLOAD_T *extra = (MP4_PAYLOAD_T *)track->priv->module->extra; + PARAMETER_T param; + uint32_t config_len; + VC_CONTAINER_STATUS_T status; + uint8_t *config; + VC_CONTAINER_BITS_T bit_stream; + + param.name = "config"; + if (!vc_containers_list_find_entry(params, ¶m) || !param.value) + { + LOG_ERROR(p_ctx, "MPEG-4: config parameter missing"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + config_len = strlen(param.value); + if (config_len & 1) + { + LOG_ERROR(p_ctx, "MPEG-4: config parameter invalid"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + config_len /= 2; + + /* Copy AudioSpecificConfig into track extradata, to be decoded by client */ + status = vc_container_track_allocate_extradata(p_ctx, track, config_len); + if(status != VC_CONTAINER_SUCCESS) return status; + + config = track->priv->extradata; + track->format->extradata_size = config_len; + hex_to_byte_buffer(param.value, config, config_len); + + /* Decode config locally, to determine sample rate, etc. */ + BITS_INIT(p_ctx, &bit_stream, config, config_len); + + switch (extra->stream_type) + { + case MPEG4_AUDIO_STREAM: + if (!mp4_decode_audio_config(p_ctx, track, &bit_stream)) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + break; + default: + /* Other stream types not yet supported */ + LOG_ERROR(p_ctx, "MPEG-4: stream type %d not supported", extra->stream_type); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * MP4 mode comparison function. + * Compare two MP4 mode structures and return whether the first is less than, + * equal to or greater than the second. + * + * @param first The first structure to be compared. + * @param second The second structure to be compared. + * @return Negative if first is less than second, positive if first is greater + * and zero if they are equal. + */ +static int mp4_mode_comparator(const MP4_MODE_ENTRY_T *a, const MP4_MODE_ENTRY_T *b) +{ + return strcasecmp(a->name, b->name); +} + +/**************************************************************************//** + * Get and store the MP4 mode, if recognised, from the URI parameters. + * + * @param p_ctx The RTP container context. + * @param track The track being constructed. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_get_mode(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + MP4_PAYLOAD_T *extra = (MP4_PAYLOAD_T *)track->priv->module->extra; + PARAMETER_T param; + MP4_MODE_ENTRY_T mode_entry; + + param.name = "mode"; + if (!vc_containers_list_find_entry(params, ¶m) || !param.value) + { + LOG_ERROR(p_ctx, "MPEG-4: mode parameter missing"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + +#ifdef RTP_DEBUG + vc_containers_list_validate(&mp4_mode_lookup); +#endif + + mode_entry.name = param.value; + if (!vc_containers_list_find_entry(&mp4_mode_lookup, &mode_entry)) + { + LOG_ERROR(p_ctx, "MPEG-4: Unrecognised mode parameter \"%s\"", mode_entry.name); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + extra->mode = mode_entry.mode; + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Check URI parameters for unsupported features. + * + * @param p_ctx The RTP container context. + * @param track The track being constructed. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_check_unsupported_features(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + uint32_t u32_unused; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(track); + + /* Limitation: RAP flag not yet supported */ + if (rtp_get_parameter_u32(params, "randomAccessIndication", &u32_unused)) + { + LOG_ERROR(p_ctx, "MPEG-4: random access not supported"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + /* Limitation: interleaving not yet supported */ + if (rtp_get_parameter_u32(params, "maxDisplacement", &u32_unused) || + rtp_get_parameter_u32(params, "de-interleaveBufferSize", &u32_unused)) + { + LOG_ERROR(p_ctx, "MPEG-4: interleaved packetization not supported"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + /* Limitation: system streams not supported */ + if (rtp_get_parameter_u32(params, "streamStateIndication", &u32_unused)) + { + LOG_ERROR(p_ctx, "MPEG-4: system streams not supported"); + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Validate parameters that have been read form the URI parameter list. + * + * @param p_ctx The RTP container context. + * @param track The track being constructed. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_check_parameters(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track) +{ + MP4_PAYLOAD_T *extra = (MP4_PAYLOAD_T *)track->priv->module->extra; + + switch (extra->mode) + { + case MP4_CELP_CBR_MODE: + if (!extra->constant_size) + { + LOG_ERROR(p_ctx, "MPEG-4: CELP-cbr requires constantSize parameter."); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + break; + case MP4_CELP_VBR_MODE: + case MP4_AAC_LBR_MODE: + if (extra->size_length != 6 || extra->index_length != 2 || extra->index_delta_length != 2) + { + LOG_ERROR(p_ctx, "MPEG-4: CELP-vbr/AAC-lbr invalid lengths (%u/%u/%u)", + extra->size_length, extra->index_length, extra->index_delta_length); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + break; + case MP4_AAC_HBR_MODE: + if (extra->size_length != 13 || extra->index_length != 3 || extra->index_delta_length != 3) + { + LOG_ERROR(p_ctx, "MPEG-4: AAC-hbr invalid lengths (%u/%u/%u)", + extra->size_length, extra->index_length, extra->index_delta_length); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + break; + default: /* MP4_GENERIC_MODE */ + if (extra->size_length > 32 || extra->index_length > 32 || extra->index_delta_length > 32) + { + LOG_ERROR(p_ctx, "MPEG-4: generic invalid lengths (%u/%u/%u)", + extra->size_length, extra->index_length, extra->index_delta_length); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + } + + if (extra->cts_delta_length > 32 || extra->dts_delta_length > 32) + { + LOG_ERROR(p_ctx, "MPEG-4: CTS/DTS invalid lengths (%u/%u)", + extra->cts_delta_length, extra->dts_delta_length); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Initialise payload bit stream for a new RTP packet. + * + * @param p_ctx The RTP container context. + * @param t_module The track module with the new RTP packet. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_new_rtp_packet(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module) +{ + VC_CONTAINER_BITS_T *payload = &t_module->payload; + MP4_PAYLOAD_T *extra = (MP4_PAYLOAD_T *)t_module->extra; + VC_CONTAINER_BITS_T *au_headers = &extra->au_headers; + + /* There will be an AU header section if any of its fields are non-zero. */ + if (extra->size_length || extra->index_length || extra->cts_delta_length || extra->dts_delta_length) + { + uint32_t au_headers_length; + + /* Calculate how far to advance the payload, to get past the AU headers */ + au_headers_length = BITS_READ_U32(p_ctx, payload, 16, "AU headers length"); + au_headers_length = BITS_TO_BYTES(au_headers_length); /* Round up to bytes */ + + /* Record where the AU headers are in the payload */ + BITS_INIT(p_ctx, au_headers, BITS_CURRENT_POINTER(p_ctx, payload), au_headers_length); + BITS_SKIP_BYTES(p_ctx, &t_module->payload, au_headers_length, "Move payload past AU headers"); + } + + /* Skip the auxiliary section, if present */ + if (extra->auxiliary_length) + { + uint32_t auxiliary_data_size; + + auxiliary_data_size = BITS_READ_U32(p_ctx, payload, extra->auxiliary_length, "Auxiliary length"); + auxiliary_data_size = BITS_TO_BYTES(auxiliary_data_size); /* Round up to bytes */ + BITS_SKIP_BYTES(p_ctx, payload, auxiliary_data_size, "Auxiliary data"); + } + + return BITS_VALID(p_ctx, payload) ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FORMAT_INVALID; +} + +/**************************************************************************//** + * Read a flagged delta from an AU header bit stream. + * A flagged delta is an optional value in the stream that is preceded by a + * flag bit that indicates whether the value is present in the stream. If the + * length of the value is zero bits, the flag is never present. + * + * @pre The delta_length must be 32 or less. + * + * @param p_ctx The container context. + * @param au_headers The AU header bit stream. + * @param delta_length The number of bits in the delta value. + * @return The delta value, or zero if not present. + */ +static int32_t mp4_flagged_delta(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_BITS_T *au_headers, + uint32_t delta_length) +{ + uint32_t value = 0; + + /* Flag is only present if the delta length is non-zero */ + if (delta_length && BITS_READ_U32(p_ctx, au_headers, 1, "CTS/DTS delta present")) + { + value = BITS_READ_U32(p_ctx, au_headers, delta_length, "CTS/DTS delta"); + + /* Sign extend value based on bit length */ + if (value & (1 << (delta_length - 1))) + value |= ~((1 << delta_length) - 1); + } + + return (int32_t)value; +} + +/**************************************************************************//** + * Read next AU header from the bit stream. + * + * @param p_ctx The RTP container context. + * @param extra The MP4-specific track module information. + * @param is_first_au True if the first AU header in the packet is being read. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_next_au_header(VC_CONTAINER_T *p_ctx, + MP4_PAYLOAD_T *extra, + bool is_first_au) +{ + VC_CONTAINER_BITS_T *au_headers = &extra->au_headers; + AU_INFO_T *au_info = &extra->au_info; + + /* See RFC3550 section 3.2.1.1 */ + + if (extra->constant_size) + au_info->available = extra->constant_size; + else + au_info->available = BITS_READ_U32(p_ctx, au_headers, extra->size_length, "AU size"); + + if (is_first_au) + au_info->index = BITS_READ_U32(p_ctx, au_headers, extra->index_length, "AU index"); + else + au_info->index += BITS_READ_U32(p_ctx, au_headers, extra->index_delta_length, "AU index delta") + 1; + + au_info->cts_delta = mp4_flagged_delta(p_ctx, au_headers, extra->cts_delta_length); + au_info->dts_delta = mp4_flagged_delta(p_ctx, au_headers, extra->dts_delta_length); + + /* RAP and stream state not supported yet */ + + return BITS_VALID(p_ctx, au_headers) ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FORMAT_INVALID; +} + +/**************************************************************************//** + * MP4 payload handler. + * Extracts/skips data from the payload according to the AU headers. + * + * @param p_ctx The RTP container context. + * @param track The track being read. + * @param p_packet The container packet information, or NULL. + * @param flags The container read flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T mp4_payload_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_PACKET_T *p_packet, + uint32_t flags) +{ + VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; + VC_CONTAINER_BITS_T *payload = &t_module->payload; + MP4_PAYLOAD_T *extra = (MP4_PAYLOAD_T *)t_module->extra; + AU_INFO_T *au_info = &extra->au_info; + bool is_new_packet = BIT_IS_SET(t_module->flags, TRACK_NEW_PACKET); + uint32_t bytes_left_in_payload; + uint32_t size; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + if (is_new_packet) + { + status = mp4_new_rtp_packet(p_ctx, t_module); + if (status != VC_CONTAINER_SUCCESS) + return status; + } + + if (!au_info->available) + { + status = mp4_next_au_header(p_ctx, extra, is_new_packet); + if (status != VC_CONTAINER_SUCCESS) + return status; + } + + if (p_packet) + { + /* Adjust the packet time stamps using deltas */ + p_packet->pts += au_info->cts_delta; + p_packet->dts += au_info->dts_delta; + } + + size = au_info->available; + bytes_left_in_payload = BITS_BYTES_AVAILABLE(p_ctx, payload); + if (size > bytes_left_in_payload) + { + /* AU is fragmented across RTP packets */ + size = bytes_left_in_payload; + } + + if (p_packet && !(flags & VC_CONTAINER_READ_FLAG_SKIP)) + { + if (!(flags & VC_CONTAINER_READ_FLAG_INFO)) + { + if (size > p_packet->buffer_size) + size = p_packet->buffer_size; + + BITS_COPY_BYTES(p_ctx, payload, size, p_packet->data, "Packet data"); + } + p_packet->size = size; + } else { + BITS_SKIP_BYTES(p_ctx, payload, size, "Packet data"); + } + + if (!(flags & VC_CONTAINER_READ_FLAG_INFO)) + au_info->available -= size; + + return BITS_VALID(p_ctx, payload) ? VC_CONTAINER_SUCCESS : VC_CONTAINER_ERROR_FORMAT_INVALID; +} + +/***************************************************************************** +Functions exported as part of the RTP parameter handler API + *****************************************************************************/ + +/**************************************************************************//** + * MP4 parameter handler. + * Parses the URI parameters to set up the track for an MP4 stream. + * + * @param p_ctx The reader context. + * @param track The track to be updated. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +VC_CONTAINER_STATUS_T mp4_parameter_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + MP4_PAYLOAD_T *extra; + VC_CONTAINER_STATUS_T status; + + /* See RFC3640, section 4.1, for parameter names and details. */ + extra = (MP4_PAYLOAD_T *)malloc(sizeof(MP4_PAYLOAD_T)); + if (!extra) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + track->priv->module->extra = extra; + memset(extra, 0, sizeof(MP4_PAYLOAD_T)); + + /* Mandatory parameters */ + status = mp4_get_stream_type(p_ctx, track, params); + if (status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_get_config(p_ctx, track, params); + if (status != VC_CONTAINER_SUCCESS) return status; + + status = mp4_get_mode(p_ctx, track, params); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* Unsupported parameters */ + status = mp4_check_unsupported_features(p_ctx, track, params); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* Optional parameters */ + rtp_get_parameter_u32(params, "sizeLength", &extra->size_length); + rtp_get_parameter_u32(params, "indexLength", &extra->index_length); + rtp_get_parameter_u32(params, "indexDeltaLength", &extra->index_delta_length); + rtp_get_parameter_u32(params, "CTSDeltaLength", &extra->cts_delta_length); + rtp_get_parameter_u32(params, "DTSDeltaLength", &extra->dts_delta_length); + rtp_get_parameter_u32(params, "objectType", &extra->object_type); + rtp_get_parameter_u32(params, "constantSize", &extra->constant_size); + rtp_get_parameter_u32(params, "constantDuration", &extra->constant_duration); + rtp_get_parameter_u32(params, "auxiliaryDataSizeLength", &extra->auxiliary_length); + + if (extra->constant_size && extra->size_length) + { + LOG_ERROR(p_ctx, "MPEG4: constantSize and sizeLength cannot both be set."); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + status = mp4_check_parameters(p_ctx, track); + if (status != VC_CONTAINER_SUCCESS) return status; + + track->priv->module->payload_handler = mp4_payload_handler; + + return VC_CONTAINER_SUCCESS; +} diff --git a/containers/rtp/rtp_mpeg4.h b/containers/rtp/rtp_mpeg4.h new file mode 100644 index 0000000..99181bb --- /dev/null +++ b/containers/rtp/rtp_mpeg4.h @@ -0,0 +1,42 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _RTP_MPEG4_H_ +#define _RTP_MPEG4_H_ + +#include "containers/containers.h" +#include "containers/core/containers_list.h" + +/** MPEG-4 parameter handler + * + * \param p_ctx Container context. + * \param track Track data. + * \param params Parameter list. + * \return Status of decoding the MPEG-4 parameters. */ +VC_CONTAINER_STATUS_T mp4_parameter_handler(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); + +#endif /* _RTP_MPEG4_H_ */ diff --git a/containers/rtp/rtp_priv.h b/containers/rtp/rtp_priv.h new file mode 100644 index 0000000..c54bfe8 --- /dev/null +++ b/containers/rtp/rtp_priv.h @@ -0,0 +1,111 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _RTP_PRIV_H_ +#define _RTP_PRIV_H_ + +#include "containers/containers.h" + +#include "containers/core/containers_private.h" +#include "containers/core/containers_bits.h" +#include "containers/core/containers_list.h" + +typedef VC_CONTAINER_STATUS_T (*PAYLOAD_HANDLER_T)(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, VC_CONTAINER_PACKET_T *p_packet, uint32_t flags); + +/** Parameter list entry type. */ +typedef struct parameter_tag +{ + const char *name; + const char *value; +} PARAMETER_T; + +/** Prototype for MIME parameter handling. + * Each MIME type has a certain set of parameter names it uses, so a handler is + * needed for each type. This is that handler's prototype. + */ +typedef VC_CONTAINER_STATUS_T (*PARAMETER_HANDLER_T)(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); + +/** Track module flag bit numbers (up to seven) */ +typedef enum +{ + TRACK_SSRC_SET = 0, + TRACK_HAS_MARKER, + TRACK_NEW_PACKET, +} track_module_flag_bit_t; + +/** RTP track data */ +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + PAYLOAD_HANDLER_T payload_handler; /**< Extracts the data from the payload */ + uint8_t *buffer; /**< Buffer into which the RTP packet is read */ + VC_CONTAINER_BITS_T payload; /**< Payload bit bit_stream */ + uint8_t flags; /**< Combination of track module flags */ + uint8_t payload_type; /**< The expected payload type */ + uint16_t max_seq_num; /**< Highest seq. number seen */ + uint32_t timestamp; /**< RTP timestamp of packet */ + uint32_t timestamp_base; /**< RTP timestamp value that equates to time zero */ + uint32_t last_timestamp_top; /**< Top two bits of RTP timestamp of previous packet */ + uint32_t timestamp_wraps; /**< Count of the times that the timestamp has wrapped */ + uint32_t timestamp_clock; /**< Clock frequency of RTP timestamp values */ + uint32_t expected_ssrc; /**< The expected SSRC, if set */ + uint32_t base_seq; /**< Base seq number */ + uint32_t bad_seq; /**< Last 'bad' seq number + 1 */ + uint32_t probation; /**< Sequential packets till source is valid */ + uint32_t received; /**< RTP packets received */ + void *extra; /**< Payload specific data */ +} VC_CONTAINER_TRACK_MODULE_T; + +/** Determine minimum number of bytes needed to hold a number of bits */ +#define BITS_TO_BYTES(X) (((X) + 7) >> 3) + +/** Collection of bit manipulation routines */ +/* @{ */ +#define SET_BIT(V, B) V |= (1 << (B)) +#define CLEAR_BIT(V, B) V &= ~(1 << (B)) +#define BIT_IS_SET(V, B) (!(!((V) & (1 << (B))))) +#define BIT_IS_CLEAR(V, B) (!((V) & (1 << (B)))) +/* }@ */ + + +/** Get a parameter's value as a decimal number. + * + * \param param_list The list of parameter name/value pairs. + * \param name The paramter's name. + * \param value Where to put the converted value. + * \return True if successful, false if the parameter was not found or didn't convert. */ +bool rtp_get_parameter_u32(const VC_CONTAINERS_LIST_T *param_list, const char *name, uint32_t *value); + +/** Get a parameter's value as a hexadecimal number. + * + * \param param_list The list of parameter name/value pairs. + * \param name The paramter's name. + * \param value Where to put the converted value. + * \return True if successful, false if the parameter was not found or didn't convert. */ +bool rtp_get_parameter_x32(const VC_CONTAINERS_LIST_T *param_list, const char *name, uint32_t *value); + +#endif /* _RTP_PRIV_H_ */ diff --git a/containers/rtp/rtp_reader.c b/containers/rtp/rtp_reader.c new file mode 100644 index 0000000..9ce4a59 --- /dev/null +++ b/containers/rtp/rtp_reader.c @@ -0,0 +1,1158 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include + +#define CONTAINER_IS_BIG_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#define CONTAINER_HELPER_LOG_INDENT(a) 0 +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_uri.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_bits.h" +#include "containers/core/containers_list.h" + +#include "rtp_priv.h" +#include "rtp_mpeg4.h" +#include "rtp_h264.h" + +#ifdef _DEBUG +/* Validates static sorted lists are correctly constructed */ +#define RTP_DEBUG 1 +#endif + +/****************************************************************************** +Configurable defines and constants. +******************************************************************************/ + +/** Maximum size of an RTP packet */ +#define MAXIMUM_PACKET_SIZE 2048 + +/** Maximum number of RTP packets that can be missed without restarting. */ +#define MAX_DROPOUT 3000 +/** Maximum number of out of sequence RTP packets that are accepted. */ +#define MAX_MISORDER 0 +/** Minimum number of sequential packets required for an acceptable connection + * when restarting. */ +#define MIN_SEQUENTIAL 2 + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +#define RTP_SCHEME "rtp:" + +/** The RTP PKT scheme is used with test pkt files */ +#define RTP_PKT_SCHEME "rtppkt:" + +/** \name RTP URI parameter names + * @{ */ +#define PAYLOAD_TYPE_NAME "rtppt" +#define MIME_TYPE_NAME "mime-type" +#define CHANNELS_NAME "channels" +#define RATE_NAME "rate" +#define SSRC_NAME "ssrc" +#define SEQ_NAME "seq" +/* @} */ + +/** A sentinel codec that is not supported */ +#define UNSUPPORTED_CODEC VC_FOURCC(0,0,0,0) + +/** Evaluates to true if the given payload type is in the supported static audio range. */ +#define IS_STATIC_AUDIO_TYPE(PT) ((PT) < countof(static_audio_payload_types)) + +/** Payload type number for the first static video type */ +#define FIRST_STATIC_VIDEO_TYPE 24 +/** Evaluates to true if the given payload type is in the supported static video range. */ +#define IS_STATIC_VIDEO_TYPE(PT) ((PT) >= FIRST_STATIC_VIDEO_TYPE && \ + (PT) < (FIRST_STATIC_VIDEO_TYPE + countof(static_video_payload_types))) + +/** Evaluates to true if the given payload type is in the dynamic range. */ +#define IS_DYNAMIC_TYPE(PT) ((PT) >= 96 && (PT) < 128) + +/** All sequence numbers are modulo this value. */ +#define RTP_SEQ_MOD (1 << 16) + +/** All the static video payload types use a 90kHz timestamp clock */ +#define STATIC_VIDEO_TIMESTAMP_CLOCK 90000 + +/** Number of microseconds in a second, used to convert RTP timestamps to microseconds */ +#define MICROSECONDS_PER_SECOND 1000000 + +/****************************************************************************** +Type definitions +******************************************************************************/ + +/** \name MIME type parameter handlers + * Function prototypes for payload parameter handlers */ +/* @{ */ +static VC_CONTAINER_STATUS_T audio_parameter_handler(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); +static VC_CONTAINER_STATUS_T l8_parameter_handler(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); +static VC_CONTAINER_STATUS_T l16_parameter_handler(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params); +/* @} */ + +/** \name MIME type payload handlers */ +/* @{ */ +static VC_CONTAINER_STATUS_T l16_payload_handler(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags); +/* @} */ + +/** Static audio payload type data */ +typedef struct audio_payload_type_data_tag +{ + VC_CONTAINER_FOURCC_T codec; /**< FourCC codec for this payload type */ + uint32_t channels; /**< Number of audio channels */ + uint32_t sample_rate; /**< Sample rate */ + uint32_t bits_per_sample; /**< Bits per sample, or 1 if not applicable */ + PARAMETER_HANDLER_T param_handler; /**< Optional parameter handler */ + PAYLOAD_HANDLER_T payload_handler; /**< Optional payload handler */ +} AUDIO_PAYLOAD_TYPE_DATA_T; + +/** The details for the statically defined audio payload types from RFC3551 */ +static AUDIO_PAYLOAD_TYPE_DATA_T static_audio_payload_types[] = +{ + { VC_CONTAINER_CODEC_MULAW, 1, 8000, 8, audio_parameter_handler, NULL }, /* 0 - PCMU */ + { UNSUPPORTED_CODEC }, /* 1 - reserved */ + { UNSUPPORTED_CODEC }, /* 2 - reserved */ + { UNSUPPORTED_CODEC, 1, 8000, 1, NULL, NULL }, /* 3 - GSM */ + { UNSUPPORTED_CODEC, 1, 8000, 1, NULL, NULL }, /* 4 - G723 */ + { UNSUPPORTED_CODEC, 1, 8000, 4, NULL, NULL }, /* 5 - DVI4 */ + { UNSUPPORTED_CODEC, 1, 16000, 4, NULL, NULL }, /* 6 - DVI4 */ + { UNSUPPORTED_CODEC, 1, 8000, 1, NULL, NULL }, /* 7 - LPC */ + { VC_CONTAINER_CODEC_ALAW, 1, 8000, 8, audio_parameter_handler, NULL }, /* 8 - PCMA */ + { UNSUPPORTED_CODEC, 1, 8000, 8, NULL, NULL }, /* 9 - G722 */ + { VC_CONTAINER_CODEC_PCM_SIGNED, 2, 44100, 16, audio_parameter_handler, l16_payload_handler }, /* 10 - L16 */ + { VC_CONTAINER_CODEC_PCM_SIGNED, 1, 44100, 16, audio_parameter_handler, l16_payload_handler }, /* 11 - L16 */ + { VC_CONTAINER_CODEC_QCELP, 1, 8000, 16, NULL, NULL }, /* 12 - QCELP */ + { UNSUPPORTED_CODEC, 1, 8000, 1, NULL, NULL }, /* 13 - CN */ + { VC_CONTAINER_CODEC_MPGA, 1, 90000, 1, NULL, NULL }, /* 14 - MPA */ + { UNSUPPORTED_CODEC, 1, 8000, 1, NULL, NULL }, /* 15 - G728 */ + { UNSUPPORTED_CODEC, 1, 11025, 4, NULL, NULL }, /* 16 - DVI4 */ + { UNSUPPORTED_CODEC, 1, 22050, 4, NULL, NULL }, /* 17 - DVI4 */ + { UNSUPPORTED_CODEC, 1, 8000, 1, NULL, NULL }, /* 18 - G729 */ +}; + +/** Static video payload type data */ +typedef struct video_payload_type_data_tag +{ + VC_CONTAINER_FOURCC_T codec; /**< FourCC codec for this payload type */ + PARAMETER_HANDLER_T param_handler; /**< Optional parameter handler */ + PAYLOAD_HANDLER_T payload_handler; /**< Optional payload handler */ +} VIDEO_PAYLOAD_TYPE_DATA_T; + +/** The details for the statically defined video payload types from RFC3551 */ +static VIDEO_PAYLOAD_TYPE_DATA_T static_video_payload_types[] = +{ + { UNSUPPORTED_CODEC }, /* 24 - unassigned */ + { UNSUPPORTED_CODEC }, /* 25 - CelB */ + { UNSUPPORTED_CODEC }, /* 26 - JPEG */ + { UNSUPPORTED_CODEC }, /* 27 - unassigned */ + { UNSUPPORTED_CODEC }, /* 28 - nv */ + { UNSUPPORTED_CODEC }, /* 29 - unassigned */ + { UNSUPPORTED_CODEC }, /* 30 - unassigned */ + { UNSUPPORTED_CODEC }, /* 31 - H261 */ + { VC_CONTAINER_CODEC_MP2V, NULL, NULL }, /* 32 - MPV */ + { UNSUPPORTED_CODEC }, /* 33 - MP2T */ + { VC_CONTAINER_CODEC_H263, NULL, NULL } /* 34 - H263 */ +}; + +/** MIME type details */ +typedef struct mime_type_data_tag +{ + const char *name; /**< Name of MIME type */ + VC_CONTAINER_ES_TYPE_T es_type; /**< Elementary stream type */ + VC_CONTAINER_FOURCC_T codec; /**< Codec to be used */ + PARAMETER_HANDLER_T param_handler; /**< Parameter handler for this MIME type */ +} MIME_TYPE_DATA_T; + +/** Comparator for MIME type details. */ +static int mime_type_data_comparator(const MIME_TYPE_DATA_T *a, const MIME_TYPE_DATA_T *b); + +/** Dynamic audio payload details + * Note: case-insensitive sort by name */ +static MIME_TYPE_DATA_T dynamic_mime_details[] = { + { "audio/l16", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_PCM_SIGNED, l16_parameter_handler }, + { "audio/l8", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_PCM_SIGNED, l8_parameter_handler }, + { "audio/mpeg4-generic", VC_CONTAINER_ES_TYPE_AUDIO, VC_CONTAINER_CODEC_MP4A, mp4_parameter_handler }, + { "video/h264", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_H264, h264_parameter_handler }, + { "video/mpeg4-generic", VC_CONTAINER_ES_TYPE_VIDEO, VC_CONTAINER_CODEC_MP4V, mp4_parameter_handler }, +}; + +/** Sorted list of dynamic MIME type details */ +VC_CONTAINERS_STATIC_LIST(dynamic_mime, dynamic_mime_details, mime_type_data_comparator); + +/** RTP reader data. */ +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T rtp_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/**************************************************************************//** + * Parameter comparison function. + * Compare two parameter structures and return whether the first is less than, + * equal to or greater than the second. + * + * @param first The first structure to be compared. + * @param second The second structure to be compared. + * @return Negative if first is less than second, positive if first is greater + * and zero if they are equal. + */ +static int parameter_comparator(const PARAMETER_T *first, const PARAMETER_T *second) +{ + return strcasecmp(first->name, second->name); +} + +/**************************************************************************//** + * Creates and populates a parameter list from a URI structure. + * The list does not copy the parameter strings themselves, so the URI structure + * must be retained (and its parameters unmodified) while the list is in use. + * + * @param uri The URI containing the parameters. + * @return List created from the parameters of the URI, or NULL on error. + */ +static VC_CONTAINERS_LIST_T *fill_parameter_list(VC_URI_PARTS_T *uri) +{ + uint32_t num_parameters = vc_uri_num_queries(uri); + VC_CONTAINERS_LIST_T *parameters; + uint32_t ii; + + parameters = vc_containers_list_create(num_parameters, sizeof(PARAMETER_T), (VC_CONTAINERS_LIST_COMPARATOR_T)parameter_comparator); + if (!parameters) + return NULL; + + for (ii = 0; ii < num_parameters; ii++) + { + PARAMETER_T param; + + vc_uri_query(uri, ii, ¶m.name, ¶m.value); + if (!vc_containers_list_insert(parameters, ¶m, false)) + { + vc_containers_list_destroy(parameters); + return NULL; + } + } + +#ifdef RTP_DEBUG + vc_containers_list_validate(parameters); +#endif + + return parameters; +} + +/**************************************************************************//** + * Decodes a static audio payload type into track information. + * The static parameters may be overridden by URI parameters. + * + * @param p_ctx The reader context. + * @param track The track to be populated. + * @param param_list The URI parameter list. + * @param payload_type The static payload type. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T decode_static_audio_type(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *param_list, + uint32_t payload_type) +{ + VC_CONTAINER_ES_FORMAT_T *format = track->format; + AUDIO_PAYLOAD_TYPE_DATA_T *data = &static_audio_payload_types[payload_type]; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(param_list); + + vc_container_assert(payload_type < countof(static_audio_payload_types)); + + if (data->codec == UNSUPPORTED_CODEC) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + format->codec = data->codec; + format->type->audio.channels = data->channels; + format->type->audio.sample_rate = data->sample_rate; + format->type->audio.bits_per_sample = data->bits_per_sample; + format->type->audio.block_align = data->channels * BITS_TO_BYTES(data->bits_per_sample); + track->priv->module->timestamp_clock = format->type->audio.sample_rate; + track->priv->module->payload_handler = data->payload_handler; + + if (data->param_handler) + data->param_handler(p_ctx, track, param_list); + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Decodes a static video payload type into track information. + * The static parameters may be overridden by URI parameters. + * + * @param p_ctx The reader context. + * @param track The track to be populated. + * @param param_list The URI parameter list. + * @param payload_type The static payload type. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T decode_static_video_type(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *param_list, + uint32_t payload_type) +{ + VC_CONTAINER_ES_FORMAT_T *format = track->format; + VIDEO_PAYLOAD_TYPE_DATA_T *data = &static_video_payload_types[payload_type - FIRST_STATIC_VIDEO_TYPE]; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(param_list); + + vc_container_assert(payload_type >= FIRST_STATIC_VIDEO_TYPE); + vc_container_assert(payload_type < FIRST_STATIC_VIDEO_TYPE + countof(static_video_payload_types)); + + if (data->codec == UNSUPPORTED_CODEC) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + format->codec = data->codec; + track->priv->module->timestamp_clock = STATIC_VIDEO_TIMESTAMP_CLOCK; + track->priv->module->payload_handler = data->payload_handler; + + if (data->param_handler) + data->param_handler(p_ctx, track, param_list); + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Compare two MIME type structures and return whether the first is less than, + * equal to or greater than the second. + * + * @param a The first parameter structure to be compared. + * @param b The second parameter structure to be compared. + * @return Negative if a is less than b, positive if a is greater and zero if + * they are equal. + */ +static int mime_type_data_comparator(const MIME_TYPE_DATA_T *a, const MIME_TYPE_DATA_T *b) +{ + return strcasecmp(a->name, b->name); +} + +/**************************************************************************//** + * Generic audio parameter handler. + * Updates the track information with generic audio parameters, such as "rate" + * and "channels". + * + * @param p_ctx The reader context. + * @param track The track to be updated. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T audio_parameter_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + VC_CONTAINER_AUDIO_FORMAT_T *audio = &track->format->type->audio; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + /* See RFC3555. Generic audio parameters that can override static payload + * type defaults. */ + if (rtp_get_parameter_u32(params, RATE_NAME, &audio->sample_rate)) + track->priv->module->timestamp_clock = audio->sample_rate; + if (rtp_get_parameter_u32(params, CHANNELS_NAME, &audio->channels)) + audio->block_align = audio->channels * BITS_TO_BYTES(audio->bits_per_sample); + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * L8 specific audio parameter handler. + * Updates the track information with audio parameters needed by the audio/L8 + * MIME type. For example, the "rate" parameter is mandatory. + * + * @param p_ctx The reader context. + * @param track The track to be updated. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T l8_parameter_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + VC_CONTAINER_AUDIO_FORMAT_T *audio = &track->format->type->audio; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + /* See RFC3555, section 4.1.14, for parameter names and details. */ + if (!rtp_get_parameter_u32(params, RATE_NAME, &audio->sample_rate)) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + if (!rtp_get_parameter_u32(params, CHANNELS_NAME, &audio->channels)) + audio->channels = 1; + audio->bits_per_sample = 8; + audio->block_align = audio->channels; + track->priv->module->timestamp_clock = audio->sample_rate; + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * L16 specific audio parameter handler. + * Updates the track information with audio parameters needed by the audio/L16 + * MIME type. For example, the "rate" parameter is mandatory. + * + * @param p_ctx The reader context. + * @param track The track to be updated. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T l16_parameter_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *params) +{ + VC_CONTAINER_AUDIO_FORMAT_T *audio = &track->format->type->audio; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + /* See RFC3555, section 4.1.15, for parameter names and details. */ + if (!rtp_get_parameter_u32(params, RATE_NAME, &audio->sample_rate)) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + if (!rtp_get_parameter_u32(params, CHANNELS_NAME, &audio->channels)) + audio->channels = 1; + audio->bits_per_sample = 16; + audio->block_align = audio->channels * 2; + track->priv->module->timestamp_clock = audio->sample_rate; + track->priv->module->payload_handler = l16_payload_handler; + + /* TODO: add support for "channel-order" to set channel mapping */ + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Decode a dynamic payload type from parameters. + * Populates the track information with data for supported dynamic media types. + * + * @param p_ctx The reader context. + * @param track The track to be updated. + * @param param_list The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T decode_dynamic_type(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *param_list) +{ + VC_CONTAINER_ES_FORMAT_T *format = track->format; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + PARAMETER_T mime_type; + MIME_TYPE_DATA_T mime_details; + + /* Get MIME type parameter */ + mime_type.name = MIME_TYPE_NAME; + if (!vc_containers_list_find_entry(param_list, &mime_type)) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + +#ifdef RTP_DEBUG + vc_containers_list_validate(&dynamic_mime); +#endif + + /* Look up MIME type to see if it can be handled */ + mime_details.name = mime_type.value; + if (!vc_containers_list_find_entry(&dynamic_mime, &mime_details)) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + format->codec = mime_details.codec; + format->es_type = mime_details.es_type; + + /* Default number of channels for audio is one */ + if (mime_details.es_type == VC_CONTAINER_ES_TYPE_AUDIO) + format->type->audio.channels = 1; + + /* Lete MIME type specific parameter handler deal with any other parameters */ + status = mime_details.param_handler(p_ctx, track, param_list); + + /* Ensure that the sample rate has been set for audio formats */ + if (mime_details.es_type == VC_CONTAINER_ES_TYPE_AUDIO && !format->type->audio.sample_rate) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + return status; +} + +/**************************************************************************//** + * Decode the RTP payload type. + * Populates track information with data from static tables and the URI + * parameter list, according to the payload and MIME types. + * + * @param p_ctx The reader context. + * @param track The track to be updated. + * @param params The URI parameter list. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T decode_payload_type(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + const VC_CONTAINERS_LIST_T *param_list, + uint32_t payload_type) +{ + VC_CONTAINER_TRACK_MODULE_T *module = track->priv->module; + VC_CONTAINER_STATUS_T status; + + if (IS_STATIC_AUDIO_TYPE(payload_type)) + status = decode_static_audio_type(p_ctx, track, param_list, payload_type); + else if (IS_STATIC_VIDEO_TYPE(payload_type)) + status = decode_static_video_type(p_ctx, track, param_list, payload_type); + else if (IS_DYNAMIC_TYPE(payload_type)) + status = decode_dynamic_type(p_ctx, track, param_list); + else + status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + module->payload_type = (uint8_t)payload_type; + + return status; +} + +/**************************************************************************//** + * Initialises the RTP sequence number algorithm with a new sequence number. + * + * @param t_module The track module. + * @param seq The new sequence number. + */ +static void init_sequence_number(VC_CONTAINER_TRACK_MODULE_T *t_module, + uint16_t seq) +{ + t_module->base_seq = seq; + t_module->max_seq_num = seq; + t_module->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */ + t_module->received = 0; +} + +/**************************************************************************//** + * Checks whether the sequence number for a packet is acceptable or not. + * The packet will be unacceptable if it is out of sequence by some degree, or + * if the packet sequence is still being established. + * + * @param t_module The track module. + * @param seq The new sequence number. + * @return True if the sequence number indicates the packet is acceptable + */ +static bool update_sequence_number(VC_CONTAINER_TRACK_MODULE_T *t_module, + uint16_t seq) +{ + uint16_t udelta = seq - t_module->max_seq_num; + + /* NOTE: This source is derived from the example code in RFC3550, section A.1 */ + + /* Source is not valid until MIN_SEQUENTIAL packets with + * sequential sequence numbers have been received. */ + if (t_module->probation) + { + /* packet is in sequence */ + if (seq == t_module->max_seq_num + 1) + { + t_module->probation--; + t_module->max_seq_num = seq; + LOG_INFO(0, "RTP: Probation, %u more packet(s) to go at 0x%4.4hx", t_module->probation, seq); + + if (!t_module->probation) + { + init_sequence_number(t_module, seq); + t_module->received++; + return 1; + } + } else { + t_module->probation = MIN_SEQUENTIAL - 1; + t_module->max_seq_num = seq; + LOG_INFO(0, "RTP: Probation reset, wait for %u packet(s) at 0x%4.4hx", t_module->probation, seq); + } + return 0; + } else if (udelta < MAX_DROPOUT) + { + if (!udelta) + { + /* Duplicate packet, drop it */ + LOG_INFO(0, "RTP: Drop duplicate packet at 0x%4.4hx", seq); + return 0; + } + if (udelta > 1) + { + LOG_INFO(0, "RTP: Jumped by %hu packets to 0x%4.4hx", udelta, seq); + } + /* in order, with permissible gap */ + t_module->max_seq_num = seq; + } else +#if (MAX_MISORDER != 0) + /* When MAX_MISORDER is zero, always treat as out of order */ + if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) +#endif + { + /* the sequence number made a very large jump */ + if (seq == t_module->bad_seq) + { + LOG_INFO(0, "RTP: Misorder restart at 0x%4.4hx", seq); + /* Two sequential packets -- assume that the other side + * restarted without telling us so just re-sync + * (i.e., pretend this was the first packet). */ + init_sequence_number(t_module, seq); + } else { + LOG_INFO(0, "RTP: Misorder at 0x%4.4hx, expected 0x%4.4hx", seq, t_module->max_seq_num); + t_module->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1); + return 0; + } + } +#if (MAX_MISORDER != 0) + else { + /* duplicate or reordered packet */ + + /* TODO: handle out of order packets */ + } +#endif + t_module->received++; + return 1; +} + +/**************************************************************************//** + * Extract the fields of an RTP packet and validate it. + * + * @param p_ctx The reader context. + * @param t_module The track module. + * @return True if successful, false if there were not enough bits in the + * packet or the packet was invalid. + */ +static void decode_rtp_packet_header(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module) +{ + VC_CONTAINER_BITS_T *payload = &t_module->payload; + uint32_t version, has_padding, has_extension, csrc_count, has_marker; + uint32_t payload_type, ssrc; + uint16_t seq_num; + + /* Break down fixed header area into component parts */ + version = BITS_READ_U32(p_ctx, payload, 2, "Version"); + has_padding = BITS_READ_U32(p_ctx, payload, 1, "Has padding"); + has_extension = BITS_READ_U32(p_ctx, payload, 1, "Has extension"); + csrc_count = BITS_READ_U32(p_ctx, payload, 4, "CSRC count"); + has_marker = BITS_READ_U32(p_ctx, payload, 1, "Has marker"); + payload_type = BITS_READ_U32(p_ctx, payload, 7, "Payload type"); + seq_num = BITS_READ_U16(p_ctx, payload, 16, "Sequence number"); + t_module->timestamp = BITS_READ_U32(p_ctx, payload, 32, "Timestamp"); + ssrc = BITS_READ_U32(p_ctx, payload, 32, "SSRC"); + + /* If there was only a partial header, abort immediately */ + if (!BITS_VALID(p_ctx, payload)) + return; + + /* Validate version, payload type, sequence number and SSRC, if set */ + if (version != 2 || payload_type != t_module->payload_type) + { + BITS_INVALIDATE(p_ctx, payload); + return; + } + if (BIT_IS_SET(t_module->flags, TRACK_SSRC_SET) && (ssrc != t_module->expected_ssrc)) + { + LOG_DEBUG(p_ctx, "RTP: Unexpected SSRC (0x%8.8X)", ssrc); + BITS_INVALIDATE(p_ctx, payload); + return; + } + + /* Check sequence number indicates packet is usable */ + if (!update_sequence_number(t_module, seq_num)) + { + BITS_INVALIDATE(p_ctx, payload); + return; + } + + /* Adjust to account for padding, CSRCs and extension */ + if (has_padding) + { + VC_CONTAINER_BITS_T bit_stream; + uint32_t available = BITS_BYTES_AVAILABLE(p_ctx, payload); + uint8_t padding; + + BITS_COPY_STREAM(p_ctx, &bit_stream, payload); + /* The last byte of the payload is the number of padding bytes, including itself */ + BITS_SKIP_BYTES(p_ctx, &bit_stream, available - 1, "Skip to padding length"); + padding = BITS_READ_U8(p_ctx, &bit_stream, 8, "Padding length"); + + BITS_REDUCE_BYTES(p_ctx, payload, padding, "Remove padding"); + } + + /* Each CSRC is 32 bits, so shift count up to skip the right number of bits */ + BITS_SKIP(p_ctx, payload, csrc_count << 5, "CSRC section"); + + if (has_extension) + { + uint32_t extension_bits; + + /* Extension header is 16-bit ID (which isn't needed), then 16-bit length in 32-bit words */ + BITS_SKIP(p_ctx, payload, 16, "Extension ID"); + extension_bits = BITS_READ_U32(p_ctx, payload, 16, "Extension length") << 5; + BITS_SKIP(p_ctx, payload, extension_bits, "Extension data"); + } + + /* Record whether or not this RTP packet had the marker bit set */ + if (has_marker) + SET_BIT(t_module->flags, TRACK_HAS_MARKER); + else + CLEAR_BIT(t_module->flags, TRACK_HAS_MARKER); + + /* If it hasn't been set independently, use the first timestamp as a baseline */ + if (!t_module->timestamp_base) + t_module->timestamp_base = t_module->timestamp; + t_module->timestamp -= t_module->timestamp_base; +} + +/**************************************************************************//** + * Generic payload handler. + * Copies/skips data verbatim from the packet payload. + * + * @param p_ctx The reader context. + * @param track The track being read. + * @param p_packet The container packet information, or NULL. + * @param flags The container read flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T generic_payload_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_PACKET_T *p_packet, + uint32_t flags) +{ + VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; + VC_CONTAINER_BITS_T *payload = &t_module->payload; + uint32_t size; + + VC_CONTAINER_PARAM_UNUSED(p_ctx); + + if (!p_packet) + { + /* Skip the rest of this RTP packet */ + BITS_INVALIDATE(p_ctx, payload); + return VC_CONTAINER_SUCCESS; + } + + /* Copy as much as possible into the client packet buffer */ + size = BITS_BYTES_AVAILABLE(p_ctx, payload); + + if (flags & VC_CONTAINER_READ_FLAG_SKIP) + BITS_SKIP_BYTES(p_ctx, payload, size, "Packet data"); + else { + if (!(flags & VC_CONTAINER_READ_FLAG_INFO)) + { + if (size > p_packet->buffer_size) + size = p_packet->buffer_size; + + BITS_COPY_BYTES(p_ctx, payload, size, p_packet->data, "Packet data"); + } + p_packet->size = size; + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * L16 payload handler. + * Copies/skips data from the packet payload. On copy, swaps consecutive bytes + * in the data in order to get expected byte order. + * + * @param p_ctx The reader context. + * @param track The track being read. + * @param p_packet The container packet information, or NULL. + * @param flags The container read flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T l16_payload_handler(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + VC_CONTAINER_PACKET_T *p_packet, + uint32_t flags) +{ + VC_CONTAINER_STATUS_T status; + + /* Most aspects are handled adequately by the generic handler */ + status = generic_payload_handler(p_ctx, track, p_packet, flags); + if (status != VC_CONTAINER_SUCCESS) + return status; + + if (p_packet && !(flags & (VC_CONTAINER_READ_FLAG_SKIP | VC_CONTAINER_READ_FLAG_INFO))) + { + uint8_t *ptr, *end_ptr; + + /* Ensure packet size is even */ + p_packet->size &= ~1; + + /* Swap bytes of each sample, to get host order instead of network order */ + for (ptr = p_packet->data, end_ptr = ptr + p_packet->size; ptr < end_ptr; ptr += 2) + { + uint8_t high_byte = ptr[0]; + ptr[0] = ptr[1]; + ptr[1] = high_byte; + } + } + + return status; +} + + +/***************************************************************************** +Utility functions for use by RTP payload handlers + *****************************************************************************/ + +/**************************************************************************//** + * Gets the value of a parameter as an unsigned 32-bit decimal integer. + * + * @param param_list The URI parameter list. + * @param name The name of the parameter. + * @param value Pointer to the variable to receive the value. + * @return True if the parameter value was read and stored correctly, false + * otherwise. + */ +bool rtp_get_parameter_u32(const VC_CONTAINERS_LIST_T *param_list, + const char *name, + uint32_t *value) +{ + PARAMETER_T param; + + param.name = name; + if (vc_containers_list_find_entry(param_list, ¶m) && param.value) + { + char *end; + + *value = strtoul(param.value, &end, 10); + return (end != param.value) && (*end == '\0'); + } + + return false; +} + +/**************************************************************************//** + * Gets the value of a parameter as an unsigned 32-bit hexadecimal integer. + * + * @param param_list The URI parameter list. + * @param name The name of the parameter. + * @param value Pointer to the variable to receive the value. + * @return True if the parameter value was read and stored correctly, false + * otherwise. + */ +bool rtp_get_parameter_x32(const VC_CONTAINERS_LIST_T *param_list, + const char *name, + uint32_t *value) +{ + PARAMETER_T param; + + param.name = name; + if (vc_containers_list_find_entry(param_list, ¶m) && param.value) + { + char *end; + + *value = strtoul(param.value, &end, 16); + return (end != param.value) && (*end == '\0'); + } + + return false; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ + +/**************************************************************************//** + * Read/skip data from the container. + * Can also be used to query information about the next block of data. + * + * @param p_ctx The reader context. + * @param p_packet The container packet information, or NULL. + * @param flags The container read flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtp_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, + uint32_t flags ) +{ + VC_CONTAINER_TRACK_T *track; + VC_CONTAINER_TRACK_MODULE_T *t_module; + VC_CONTAINER_STATUS_T status; + + if((flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) && p_packet->track) + return VC_CONTAINER_ERROR_INVALID_ARGUMENT; + + track = p_ctx->tracks[0]; + t_module = track->priv->module; + + CLEAR_BIT(t_module->flags, TRACK_NEW_PACKET); + + while (!BITS_AVAILABLE(p_ctx, &t_module->payload)) + { + uint32_t bytes_read; + + /* No data left from last RTP packet, get another one */ + bytes_read = READ_BYTES(p_ctx, t_module->buffer, MAXIMUM_PACKET_SIZE); + if (!bytes_read) + return STREAM_STATUS(p_ctx); + + BITS_INIT(p_ctx, &t_module->payload, t_module->buffer, bytes_read); + + decode_rtp_packet_header(p_ctx, t_module); + SET_BIT(t_module->flags, TRACK_NEW_PACKET); + } + + if (p_packet) + { + uint32_t timestamp_top = t_module->timestamp >> 30; + + /* Determine whether timestamp has wrapped forwards or backwards around zero */ + if ((timestamp_top == 0) && (t_module->last_timestamp_top == 3)) + t_module->timestamp_wraps++; + else if ((timestamp_top == 3) && (t_module->last_timestamp_top == 0)) + t_module->timestamp_wraps--; + t_module->last_timestamp_top = timestamp_top; + + p_packet->dts = p_packet->pts = ((int64_t)t_module->timestamp_wraps << 32) | t_module->timestamp; + p_packet->track = 0; + p_packet->flags = 0; + } + + status = t_module->payload_handler(p_ctx, track, p_packet, flags); + if (p_packet && status == VC_CONTAINER_SUCCESS) + { + /* Adjust timestamps from RTP clock rate to microseconds */ + p_packet->pts = p_packet->pts * MICROSECONDS_PER_SECOND / t_module->timestamp_clock; + p_packet->dts = p_packet->dts * MICROSECONDS_PER_SECOND / t_module->timestamp_clock; + } + + STREAM_STATUS(p_ctx) = status; + return status; +} + +/**************************************************************************//** + * Seek over data in the container. + * + * @param p_ctx The reader context. + * @param p_offset The seek offset. + * @param mode The seek mode. + * @param flags The seek flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtp_reader_seek( VC_CONTAINER_T *p_ctx, + int64_t *p_offset, + VC_CONTAINER_SEEK_MODE_T mode, + VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(p_offset); + VC_CONTAINER_PARAM_UNUSED(mode); + VC_CONTAINER_PARAM_UNUSED(flags); + + /* RTP is a non-seekable container */ + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; +} + +/**************************************************************************//** + * Apply a control operation to the container. + * + * @param p_ctx The reader context. + * @param operation The control operation. + * @param args Optional additional arguments for the operation. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtp_reader_control( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_CONTROL_T operation, + va_list args) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_TRACK_MODULE_T *t_module = p_ctx->tracks[0]->priv->module; + + switch (operation) + { + case VC_CONTAINER_CONTROL_SET_TIMESTAMP_BASE: + { + t_module->timestamp_base = va_arg(args, uint32_t); + if (!t_module->timestamp_base) + t_module->timestamp_base = 1; /* Zero is used to mean "not set" */ + status = VC_CONTAINER_SUCCESS; + } + break; + case VC_CONTAINER_CONTROL_SET_NEXT_SEQUENCE_NUMBER: + { + init_sequence_number(t_module, (uint16_t)va_arg(args, uint32_t)); + t_module->probation = 0; + status = VC_CONTAINER_SUCCESS; + } + break; + case VC_CONTAINER_CONTROL_SET_SOURCE_ID: + { + t_module->expected_ssrc = va_arg(args, uint32_t); + SET_BIT(t_module->flags, TRACK_SSRC_SET); + status = VC_CONTAINER_SUCCESS; + } + break; + default: + status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + } + + return status; +} + +/**************************************************************************//** + * Close the container. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtp_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + vc_container_assert(p_ctx->tracks_num < 2); + + if (p_ctx->tracks_num) + { + void *payload_extra; + + vc_container_assert(module); + vc_container_assert(module->track); + vc_container_assert(module->track->priv); + vc_container_assert(module->track->priv->module); + + payload_extra = module->track->priv->module->extra; + if (payload_extra) + free(payload_extra); + vc_container_free_track(p_ctx, module->track); + } + p_ctx->tracks = NULL; + p_ctx->tracks_num = 0; + if (module) free(module); + p_ctx->priv->module = 0; + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Open the container. + * Uses the I/O URI and/or data to configure the container. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +VC_CONTAINER_STATUS_T rtp_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_TRACK_T *track = 0; + VC_CONTAINER_TRACK_MODULE_T *t_module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINERS_LIST_T *parameters = NULL; + uint32_t payload_type; + uint32_t initial_seq_num; + + /* Check the URI scheme looks valid */ + if (!vc_uri_scheme(p_ctx->priv->uri) || + (strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTP_SCHEME) && + strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTP_PKT_SCHEME))) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Make the query/parameter list more easily searchable */ + parameters = fill_parameter_list(p_ctx->priv->uri); + if (!parameters) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + + /* Payload type parameter is mandatory and must fit in 7 bits */ + if (!rtp_get_parameter_u32(parameters, PAYLOAD_TYPE_NAME, &payload_type) || payload_type > 127) + { + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + goto error; + } + + /* Allocate our context */ + module = (VC_CONTAINER_MODULE_T *)malloc(sizeof(VC_CONTAINER_MODULE_T)); + if (!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = &module->track; + + /* Allocate the track, including space for reading an RTP packet on the end */ + track = vc_container_allocate_track(p_ctx, sizeof(VC_CONTAINER_TRACK_MODULE_T) + MAXIMUM_PACKET_SIZE); + if (!track) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto error; + } + module->track = track; + p_ctx->tracks_num = 1; + t_module = track->priv->module; + + /* Initialise the track data */ + t_module->buffer = (uint8_t *)(t_module + 1); + status = decode_payload_type(p_ctx, track, parameters, payload_type); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + vc_container_assert(t_module->timestamp_clock != 0); + + /* Default to a generic, unstructured payload handler */ + if (!t_module->payload_handler) + t_module->payload_handler = generic_payload_handler; + + if (rtp_get_parameter_x32(parameters, SSRC_NAME, &t_module->expected_ssrc)) + SET_BIT(t_module->flags, TRACK_SSRC_SET); + + t_module->probation = MIN_SEQUENTIAL; + if (rtp_get_parameter_u32(parameters, SEQ_NAME, &initial_seq_num)) + { + /* If an initial sequence number is provided, avoid probation period */ + t_module->max_seq_num = (uint16_t)initial_seq_num; + t_module->probation = 0; + } + + track->is_enabled = true; + + vc_containers_list_destroy(parameters); + + p_ctx->priv->pf_close = rtp_reader_close; + p_ctx->priv->pf_read = rtp_reader_read; + p_ctx->priv->pf_seek = rtp_reader_seek; + p_ctx->priv->pf_control = rtp_reader_control; + + return VC_CONTAINER_SUCCESS; + +error: + if (parameters) vc_containers_list_destroy(parameters); + if(status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_EOS) + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + LOG_DEBUG(p_ctx, "error opening RTP (%i)", status); + rtp_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open rtp_reader_open +#endif diff --git a/containers/rtsp/CMakeLists.txt b/containers/rtsp/CMakeLists.txt new file mode 100644 index 0000000..5c386f6 --- /dev/null +++ b/containers/rtsp/CMakeLists.txt @@ -0,0 +1,14 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +set(rtsp_SRCS ${rtsp_SRCS} rtsp_reader.c) +add_library(reader_rtsp ${LIBRARY_TYPE} ${rtsp_SRCS}) + +target_link_libraries(reader_rtsp containers) + +install(TARGETS reader_rtsp DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/rtsp/rtsp_reader.c b/containers/rtsp/rtsp_reader.c new file mode 100644 index 0000000..2d96234 --- /dev/null +++ b/containers/rtsp/rtsp_reader.c @@ -0,0 +1,1983 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include + +#define CONTAINER_IS_BIG_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#define CONTAINER_HELPER_LOG_INDENT(a) 0 +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_list.h" +#include "containers/core/containers_uri.h" + +/****************************************************************************** +Configurable defines and constants. +******************************************************************************/ + +/** Maximum number of tracks allowed in an RTSP reader */ +#define RTSP_TRACKS_MAX 4 + +/** Space for sending requests and receiving responses */ +#define COMMS_BUFFER_SIZE 2048 + +/** Largest allowed RTSP URI. Must be substantially smaller than COMMS_BUFFER_SIZE + * to allow for the headers that may be sent. */ +#define RTSP_URI_LENGTH_MAX 1024 + +/** Maximum allowed length for the Session: header recevied in a SETUP response, + * This is to ensure comms buffer is not overflowed. */ +#define SESSION_HEADER_LENGTH_MAX 100 + +/** Number of milliseconds to block trying to read from the RTSP stream when no + * data is available from any of the tracks */ +#define DATA_UNAVAILABLE_READ_TIMEOUT_MS 1 + +/** Size of buffer for each track to use when receiving packets */ +#define UDP_READ_BUFFER_SIZE 520000 + +/* Arbitrary number of different dynamic ports to try */ +#define DYNAMIC_PORT_ATTEMPTS_MAX 16 + +/****************************************************************************** +Defines and constants. +******************************************************************************/ + +#define RTSP_SCHEME "rtsp:" +#define RTP_SCHEME "rtp" + +/** The RTSP PKT scheme is used with test pkt files */ +#define RTSP_PKT_SCHEME "rtsppkt:" + +#define RTSP_NETWORK_URI_START "rtsp://" +#define RTSP_NETWORK_URI_START_LENGTH (sizeof(RTSP_NETWORK_URI_START)-1) + +/** Initial capacity of header list */ +#define HEADER_LIST_INITIAL_CAPACITY 16 + +/** Format of the first line of an RTSP request */ +#define RTSP_REQUEST_LINE_FORMAT "%s %s RTSP/1.0\r\n" + +/** Format string for common headers used with all request methods. + * Note: includes double new line to terminate headers */ +#define TRAILING_HEADERS_FORMAT "CSeq: %u\r\nConnection: Keep-Alive\r\nUser-Agent: Broadcom/1.0\r\n\r\n" + +/** Format for the Transport: header */ +#define TRANSPORT_HEADER_FORMAT "Transport: RTP/AVP;unicast;client_port=%hu-%hu;mode=play\r\n" + +/** Format for including Session: header. */ +#define SESSION_HEADER_FORMAT "Session: %s\r\n" + +/** \name RTSP methods, used as the first item in the request line + * @{ */ +#define DESCRIBE_METHOD "DESCRIBE" +#define SETUP_METHOD "SETUP" +#define PLAY_METHOD "PLAY" +#define TEARDOWN_METHOD "TEARDOWN" +/* @} */ + +/** \name Names of headers used by the code + * @{ */ +#define CONTENT_PSEUDOHEADER_NAME ":" +#define CONTENT_LENGTH_NAME "Content-Length" +#define CONTENT_BASE_NAME "Content-Base" +#define CONTENT_LOCATION_NAME "Content-Location" +#define RTP_INFO_NAME "RTP-Info" +#define SESSION_NAME "Session" +/* @} */ + +/** Supported RTSP major version number */ +#define RTSP_MAJOR_VERSION 1 +/** Supported RTSP minor version number */ +#define RTSP_MINOR_VERSION 0 + +/** Lowest successful status code value */ +#define RTSP_STATUS_OK 200 +/** Next failure status code after the set of successful ones */ +#define RTSP_STATUS_MULTIPLE_CHOICES 300 + +/** Maximum size of a decimal string representation of a uint16_t, plus NUL */ +#define PORT_BUFFER_SIZE 6 +/** Start of private / dynamic port region */ +#define FIRST_DYNAMIC_PORT 0xC000 +/** End of private / dynamic port region */ +#define LAST_DYNAMIC_PORT 0xFFF0 + +/** Format of RTP track file extension */ +#define RTP_PATH_EXTENSION_FORMAT ".t%u.pkt" +/** Extra space need for creating an RTP track file name from an RTSP URI path */ +#define RTP_PATH_EXTRA 17 + +/** \name RTP URI parameter names + * @{ */ +#define PAYLOAD_TYPE_NAME "rtppt" +#define MIME_TYPE_NAME "mime-type" +#define SAMPLE_RATE_NAME "rate" +#define CHANNELS_NAME "channels" +/* @} */ + +/** Largest signed 64-bit integer */ +#define MAXIMUM_INT64 (int64_t)((1ULL << 63) - 1) + +/****************************************************************************** +Type definitions +******************************************************************************/ + +typedef int (*PARSE_IS_DELIMITER_FN_T)(int char_to_test); + +typedef struct rtsp_header_tag +{ + const char *name; + char *value; +} RTSP_HEADER_T; + +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + VC_CONTAINER_T *reader; /**< RTP reader for track */ + VC_URI_PARTS_T *reader_uri; /**< URI built up from SDP and used to open reader */ + char *control_uri; /**< URI used to control track playback */ + char *session_header; /**< Session header to be used when sending control requests */ + char *payload_type; /**< RTP payload type for track */ + char *media_type; /**< MIME type for track */ + VC_CONTAINER_PACKET_T info; /**< Latest track packet info block */ + unsigned short rtp_port; /**< UDP listener port being used in RTP reader */ +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *tracks[RTSP_TRACKS_MAX]; + char *comms_buffer; /**< Buffer used for sending and receiving RTSP messages */ + VC_CONTAINERS_LIST_T *header_list; /**< Parsed response headers, pointing into comms buffer */ + uint32_t cseq_value; /**< CSeq header value for next request */ + uint16_t next_rtp_port; /**< Next RTP port to use when opening track reader */ + uint16_t media_item; /**< Current media item number during initialization */ + bool uri_has_network_info; /**< True if the RTSP URI contains network info */ + int64_t ts_base; /**< Base value for dts and pts */ + VC_CONTAINER_TRACK_MODULE_T *current_track; /**< Next track to be read, to keep info/data on same track */ +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +static int rtsp_header_comparator(const RTSP_HEADER_T *first, const RTSP_HEADER_T *second); + +VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/**************************************************************************//** + * Trim whitespace from the end and start of the string + * + * \param str String to be trimmed + * \return Trimmed string + */ +static char *rtsp_trim( char *str ) +{ + char *trim = str + strlen(str); + + /* Search backwards for first non-whitespace */ + while (--trim >= str && isspace((int)*trim)) + ; /* Everything done in the while */ + trim[1] = '\0'; + + /* Now move start of string forwards to first non-whitespace */ + trim = str; + while (isspace((int)*trim)) + trim++; + + return trim; +} + +/**************************************************************************//** + * Send out the data in the comms buffer. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_send( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t to_write; + uint32_t written; + const char *buffer = module->comms_buffer; + + /* When reading from a captured file, do not attempt to send data */ + if (!module->uri_has_network_info) + return VC_CONTAINER_SUCCESS; + + to_write = strlen(buffer); + + while (to_write) + { + written = vc_container_io_write(p_ctx->priv->io, buffer, to_write); + if (!written) + break; + to_write -= written; + buffer += written; + } + + return p_ctx->priv->io->status; +} + +/**************************************************************************//** + * Send a DESCRIBE request to the RTSP server. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_send_describe_request( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; + char *uri = p_ctx->priv->io->uri; + + if (strlen(uri) > RTSP_URI_LENGTH_MAX) + { + LOG_ERROR(p_ctx, "RTSP: URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); + return VC_CONTAINER_ERROR_URI_OPEN_FAILED; + } + + ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, DESCRIBE_METHOD, uri); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); + vc_container_assert(ptr < end); + + return rtsp_send(p_ctx); +} + +/**************************************************************************//** + * Send a SETUP request to the RTSP server. + * + * @param p_ctx The reader context. + * @param t_module The track module relating to the SETUP. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_send_setup_request( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; + char *uri = t_module->control_uri; + + if (strlen(uri) > RTSP_URI_LENGTH_MAX) + { + LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); + return VC_CONTAINER_ERROR_URI_OPEN_FAILED; + } + + ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, SETUP_METHOD, uri); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, TRANSPORT_HEADER_FORMAT, t_module->rtp_port, t_module->rtp_port + 1); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); + vc_container_assert(ptr < end); + + return rtsp_send(p_ctx); +} + +/**************************************************************************//** + * Send a PLAY request to the RTSP server. + * + * @param p_ctx The reader context. + * @param t_module The track module relating to the PLAY. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_send_play_request( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; + char *uri = t_module->control_uri; + + if (strlen(uri) > RTSP_URI_LENGTH_MAX) + { + LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); + return VC_CONTAINER_ERROR_URI_OPEN_FAILED; + } + + ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, PLAY_METHOD, uri); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, SESSION_HEADER_FORMAT, t_module->session_header); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); + vc_container_assert(ptr < end); + + return rtsp_send(p_ctx); +} + +/**************************************************************************//** + * Send a TEARDOWN request to the RTSP server. + * + * @param p_ctx The reader context. + * @param t_module The track module relating to the SETUP. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_send_teardown_request( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + char *ptr = module->comms_buffer, *end = ptr + COMMS_BUFFER_SIZE; + char *uri = t_module->control_uri; + + if (strlen(uri) > RTSP_URI_LENGTH_MAX) + { + LOG_ERROR(p_ctx, "RTSP: Control URI is too long (%d>%d)", strlen(uri), RTSP_URI_LENGTH_MAX); + return VC_CONTAINER_ERROR_URI_OPEN_FAILED; + } + + ptr += snprintf(ptr, end - ptr, RTSP_REQUEST_LINE_FORMAT, TEARDOWN_METHOD, uri); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, SESSION_HEADER_FORMAT, t_module->session_header); + if (ptr < end) + ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT, module->cseq_value++); + vc_container_assert(ptr < end); + + return rtsp_send(p_ctx); +} + +/**************************************************************************//** + * Check a response status line to see if the response is usable or not. + * Reasons for invalidity include: + * - Incorrectly formatted + * - Unsupported version + * - Status code is not in the 2xx range + * + * @param p_ctx The reader context. + * @param status_line The response status line. + * @return The resulting status of the function. + */ +static bool rtsp_successful_response_status( VC_CONTAINER_T *p_ctx, + const char *status_line) +{ + unsigned int major_version, minor_version, status_code; + + /* coverity[secure_coding] String is null-terminated */ + if (sscanf(status_line, "RTSP/%u.%u %u", &major_version, &minor_version, &status_code) != 3) + { + LOG_ERROR(p_ctx, "RTSP: Invalid response status line:\n%s", status_line); + return false; + } + + if (major_version != RTSP_MAJOR_VERSION || minor_version != RTSP_MINOR_VERSION) + { + LOG_ERROR(p_ctx, "RTSP: Unexpected response RTSP version: %u.%u", major_version, minor_version); + return false; + } + + if (status_code < RTSP_STATUS_OK || status_code >= RTSP_STATUS_MULTIPLE_CHOICES) + { + LOG_ERROR(p_ctx, "RTSP: Response status unsuccessful:\n%s", status_line); + return false; + } + + return true; +} + +/**************************************************************************//** + * Get the content length header from the response headers as an unsigned + * 32-bit integer. + * If the content length header is not found or badly formatted, zero is + * returned. + * + * @param header_list The response headers. + * @return The content length. + */ +static uint32_t rtsp_get_content_length( VC_CONTAINERS_LIST_T *header_list ) +{ + unsigned int content_length = 0; + RTSP_HEADER_T header; + + header.name = CONTENT_LENGTH_NAME; + if (header_list && vc_containers_list_find_entry(header_list, &header)) + /* coverity[secure_coding] String is null-terminated */ + sscanf(header.value, "%u", &content_length); + + return content_length; +} + +/**************************************************************************//** + * Get the session header from the response headers. + * If the session header is not found, the empty string is returned. + * + * @param header_list The response headers. + * @return The session header. + */ +static const char *rtsp_get_session_header(VC_CONTAINERS_LIST_T *header_list) +{ + RTSP_HEADER_T header; + + header.name = SESSION_NAME; + if (header_list && vc_containers_list_find_entry(header_list, &header)) + return header.value; + + return ""; +} + +/**************************************************************************//** + * Returns pointer to the string with any leading whitespace trimmed and + * terminated by a delimiter, as determined by is_delimiter_fn. The delimiter + * character replaced by NUL (to terminate the string) is returned in the + * variable pointed at by p_delimiter_replaced, if it is not NULL. + * + * The parse_str pointer is moved on to the character after the delimiter, or + * the end of the string if it is reached first. + * + * @param parse_str Pointer to the string pointer to parse. + * @param is_delimiter_fn Function to test if a character is a delimiter or not. + * @param p_delimiter_replaced Pointer to variable to receive delimiter character, or NULL. + * @return Pointer to extracted string. + */ +static char *rtsp_parse_extract(char **parse_str, + PARSE_IS_DELIMITER_FN_T is_delimiter_fn, + char *p_delimiter_replaced) +{ + char *ptr; + char *result; + + vc_container_assert(parse_str); + vc_container_assert(*parse_str); + vc_container_assert(is_delimiter_fn); + + ptr = *parse_str; + + while (isspace((int)*ptr)) + ptr++; + + result = ptr; + + while (*ptr && !(*is_delimiter_fn)(*ptr)) + ptr++; + if (p_delimiter_replaced) + *p_delimiter_replaced = *ptr; + if (*ptr) + *ptr++ = '\0'; + + *parse_str = ptr; + return result; +} + +/**************************************************************************//** + * Specialised form of rtsp_parse_extract() where the delimiter is whitespace. + * Returns pointer to the string with any leading whitespace trimmed and + * terminated by further whitespace. + * + * The parse_str pointer is moved on to the character after the delimiter, or + * the end of the string if it is reached first. + * + * @param parse_str Pointer to the string pointer to parse. + * @return Pointer to extracted string. + */ +static char *rtsp_parse_extract_ws(char **parse_str) +{ + char *ptr; + char *result; + + vc_container_assert(parse_str); + vc_container_assert(*parse_str); + + ptr = *parse_str; + + while (isspace((int)*ptr)) + ptr++; + + result = ptr; + + while (*ptr && !isspace((int)*ptr)) + ptr++; + if (*ptr) + *ptr++ = '\0'; + + *parse_str = ptr; + return result; +} + +/**************************************************************************//** + * Returns whether the given character is a parameter name delimiter or not. + * + * @param char_to_test The character under test. + * @return True if the character is a name delimiter, false if not. + */ +static int name_delimiter_fn(int char_to_test) +{ + switch (char_to_test) + { + case ' ': + case '\t': + case '=': + case ';': + return true; + default: + return false; + } +} + +/**************************************************************************//** + * Returns whether the given character is a parameter value delimiter or not. + * + * @param char_to_test The character under test. + * @return True if the character is a value delimiter, false if not. + */ +static int value_delimiter_fn(int char_to_test) +{ + switch (char_to_test) + { + case ' ': + case '\t': + case ';': + return true; + default: + return false; + } +} + +/**************************************************************************//** + * Extract a name/value pair from a given string. + * Each pair consists of a name, optionally followed by '=' and a value, with + * optional whitespace around either or both name and value. The parameter is + * terminated by a semi-colon, ';'. + * + * The parse_str pointer is moved on to the next parameter, or the end of the + * string if that is reached first. + * + * Name can be empty if there are two consecutive semi-colons, or a trailing + * semi-colon. + * + * @param parse_str Pointer to the string pointer to be parsed. + * @param p_name Pointer to where name string pointer shall be written. + * @param p_value Pointer to where value string pointer shall be written. + * @return True if the name is not empty. + */ +static bool rtsp_parse_extract_parameter(char **parse_str, char **p_name, char **p_value) +{ + char delimiter; + + vc_container_assert(parse_str); + vc_container_assert(*parse_str); + vc_container_assert(p_name); + vc_container_assert(p_value); + + /* General form of each parameter: + * [=] + * but allow for spaces before and after name and value */ + *p_name = rtsp_parse_extract(parse_str, name_delimiter_fn, &delimiter); + if (isspace((int)delimiter)) + { + /* Skip further spaces after parameter name */ + do { + delimiter = **parse_str; + if (delimiter) + (*parse_str)++; + } while (isspace((int)delimiter)); + } + + if (delimiter == '=') + { + /* Parameter value present (although may be empty) */ + *p_value = rtsp_parse_extract(parse_str, value_delimiter_fn, &delimiter); + if (isspace((int)delimiter)) + { + /* Skip spaces after parameter value */ + do { + delimiter = **parse_str; + if (delimiter) + (*parse_str)++; + } while (isspace((int)delimiter)); + } + } else { + *p_value = NULL; + } + + return (**p_name != '\0'); +} + +/**************************************************************************//** + * Parses RTP-Info header and stores relevant parts. + * + * @param header_list The response header list. + * @param t_module The track module relating to the response headers. + */ +static void rtsp_store_rtp_info(VC_CONTAINERS_LIST_T *header_list, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + RTSP_HEADER_T header; + char *ptr; + + header.name = RTP_INFO_NAME; + if (!vc_containers_list_find_entry(header_list, &header)) + return; + + ptr = header.value; + while (ptr && *ptr) + { + char *name; + char *value; + + if (!rtsp_parse_extract_parameter(&ptr, &name, &value)) + continue; + + if (strcasecmp(name, "rtptime") == 0) + { + unsigned int timestamp_base = 0; + + /* coverity[secure_coding] String is null-terminated */ + if (sscanf(value, "%u", ×tamp_base) == 1) + (void)vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_SET_TIMESTAMP_BASE, timestamp_base); + } + else if (strcasecmp(name, "seq") == 0) + { + unsigned short int sequence_number = 0; + + /* coverity[secure_coding] String is null-terminated */ + if (sscanf(value, "%hu", &sequence_number) == 1) + (void)vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_SET_NEXT_SEQUENCE_NUMBER, (uint32_t)sequence_number); + } + } +} + +/**************************************************************************//** + * Reads an RTSP response and parses it into headers and content. + * The headers and content remain stored in the comms buffer, but referenced + * by the module's header list. Content uses a special header name that cannot + * occur in the real headers. + * + * @param p_ctx The RTSP reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_read_response( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_IO_T *p_ctx_io = p_ctx->priv->io; + char *next_read = module->comms_buffer; + uint32_t space_available = COMMS_BUFFER_SIZE - 1; /* Allow for a NUL */ + uint32_t received; + char *ptr = next_read; + bool found_content = false; + RTSP_HEADER_T header; + + vc_containers_list_reset(module->header_list); + + /* Response status line doesn't need to be stored, just checked */ + header.name = NULL; + header.value = next_read; + + while (space_available) + { + received = vc_container_io_read(p_ctx_io, next_read, space_available); + if (p_ctx_io->status != VC_CONTAINER_SUCCESS) + break; + + next_read += received; + space_available -= received; + + while (!found_content && ptr < next_read) + { + switch (*ptr) + { + case ':': + if (header.value) + { + /* Just another character in the value */ + ptr++; + } else { + /* End of name, expect value next */ + *ptr++ = '\0'; + header.value = ptr; + } + break; + + case '\n': + if (header.value) + { + /* End of line while parsing the value part of the header, add name/value pair to list */ + *ptr++ = '\0'; + header.value = rtsp_trim(header.value); + if (header.name) + { + if (!vc_containers_list_insert(module->header_list, &header, false)) + { + LOG_ERROR(p_ctx, "RTSP: Failed to add <%s> header to list", header.name); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + } else { + /* Check response status line */ + if (!rtsp_successful_response_status(p_ctx, header.value)) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + /* Ready for next header */ + header.name = ptr; + header.value = NULL; + } else { + uint32_t content_length; + + /* End of line while parsing the name of a header */ + *ptr++ = '\0'; + if (*header.name && *header.name != '\r') + { + /* A non-empty name is invalid, so fail */ + LOG_ERROR(p_ctx, "RTSP: Invalid name in header - no colon:\n%s", header.name); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + /* An empty name signifies the start of the content has been found */ + found_content = true; + + /* Make a pseudo-header for the content and add it to the list */ + header.name = CONTENT_PSEUDOHEADER_NAME; + header.value = ptr; + if (!vc_containers_list_insert(module->header_list, &header, false)) + { + LOG_ERROR(p_ctx, "RTSP: Failed to add content pseudoheader to list"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + + /* Calculate how much content there is left to read, based on Content-Length header */ + content_length = rtsp_get_content_length(module->header_list); + if (ptr + content_length < next_read) + { + /* Final content byte already present, with extra data after it */ + space_available = 0; + } else { + uint32_t content_to_read = content_length - (next_read - ptr); + + if (content_to_read >= space_available) + { + LOG_ERROR(p_ctx, "RTSP: Not enough room to read content"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + + /* Restrict further reading to the number of content bytes left */ + space_available = content_to_read; + } + } + break; + + default: + /* Just another character in either the name or the value */ + ptr++; + } + } + } + + if (!space_available) + { + if (found_content) + { + /* Terminate content region */ + *next_read = '\0'; + } else { + /* Ran out of buffer space and never found the content */ + LOG_ERROR(p_ctx, "RTSP: Response header section too big / content missing"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + } + + return p_ctx_io->status; +} + +/**************************************************************************//** + * Creates a new track from an SDP media field. + * Limitation: only the first payload type of the field is used. + * + * @param p_ctx The RTSP reader context. + * @param media The media field. + * @param p_track Pointer to the variable to receive the new track pointer. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_create_track_for_media_field(VC_CONTAINER_T *p_ctx, + char *media, + VC_CONTAINER_TRACK_T **p_track ) +{ + VC_CONTAINER_TRACK_T *track = NULL; + VC_CONTAINER_TRACK_MODULE_T *t_module = NULL; + char *ptr = media; + char *media_type; + char *rtp_port; + char *transport_type; + char *payload_type; + + *p_track = NULL; + if (p_ctx->tracks_num == RTSP_TRACKS_MAX) + { + LOG_DEBUG(p_ctx, "RTSP: Too many media items in SDP data, only %d are supported.", RTSP_TRACKS_MAX); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + /* Format of media item: + * m= + * Only RTP/AVP transport and the first payload type are supported */ + media_type = rtsp_parse_extract_ws(&ptr); + rtp_port = rtsp_parse_extract_ws(&ptr); + transport_type = rtsp_parse_extract_ws(&ptr); + payload_type = rtsp_parse_extract_ws(&ptr); + if (!*media_type || !*rtp_port || strcmp(transport_type, "RTP/AVP") || !*payload_type) + { + LOG_ERROR(p_ctx, "RTSP: Failure to parse media field"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + track = vc_container_allocate_track(p_ctx, sizeof(VC_CONTAINER_TRACK_MODULE_T)); + if (!track) goto out_of_memory_error; + t_module = track->priv->module; + + /* If the port specifier is invalid, treat it as if it were zero */ + /* coverity[secure_coding] String is null-terminated */ + sscanf(rtp_port, "%hu", &t_module->rtp_port); + t_module->payload_type = payload_type; + t_module->media_type = media_type; + + t_module->reader_uri = vc_uri_create(); + if (!t_module->reader_uri) goto out_of_memory_error; + if (!vc_uri_set_scheme(t_module->reader_uri, RTP_SCHEME)) goto out_of_memory_error; + if (!vc_uri_add_query(t_module->reader_uri, PAYLOAD_TYPE_NAME, payload_type)) goto out_of_memory_error; + + p_ctx->tracks[p_ctx->tracks_num++] = track; + *p_track = track; + return VC_CONTAINER_SUCCESS; + +out_of_memory_error: + if (track) + { + if (t_module->reader_uri) + vc_uri_release(t_module->reader_uri); + vc_container_free_track(p_ctx, track); + } + LOG_ERROR(p_ctx, "RTSP: Memory allocation failure creating track"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; +} + +/**************************************************************************//** + * Returns whether the given character is a slash or not. + * + * @param char_to_test The character under test. + * @return True if the character is a slash, false if not. + */ +static int slash_delimiter_fn(int char_to_test) +{ + return char_to_test == '/'; +} + +/**************************************************************************//** + * Parse an rtpmap attribute and store values in the related track. + * + * @param p_ctx The RTSP reader context. + * @param track The track relating to the rtpmap. + * @param attribute The rtpmap attribute value. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_parse_rtpmap_attribute( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + char *attribute ) +{ + VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; + char *ptr = attribute; + char *payload_type; + char *mime_sub_type; + char *sample_rate; + char *full_mime_type; + char *channels; + + /* rtpmap attribute format: + * /[/] + * Payload type must match the one used in the media field */ + payload_type = rtsp_parse_extract_ws(&ptr); + if (strcmp(payload_type, t_module->payload_type)) + { + /* Ignore any unsupported secondary payload type attributes */ + LOG_DEBUG(p_ctx, "RTSP: Secondary payload type attribute - not supported"); + return VC_CONTAINER_SUCCESS; + } + + mime_sub_type = rtsp_parse_extract(&ptr, slash_delimiter_fn, NULL); + if (!*mime_sub_type) + { + LOG_ERROR(p_ctx, "RTSP: rtpmap: MIME type missing"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + sample_rate = rtsp_parse_extract(&ptr, slash_delimiter_fn, NULL); + if (!*sample_rate) + { + LOG_ERROR(p_ctx, "RTSP: rtpmap: sample rate missing"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + full_mime_type = (char *)malloc(strlen(t_module->media_type) + strlen(mime_sub_type) + 2); + if (!full_mime_type) + { + LOG_ERROR(p_ctx, "RTSP: Failed to allocate space for full MIME type"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + /* coverity[secure_coding] String has been allocated of the right size */ + sprintf(full_mime_type, "%s/%s", t_module->media_type, mime_sub_type); + if (!vc_uri_add_query(t_module->reader_uri, MIME_TYPE_NAME, full_mime_type)) + { + free(full_mime_type); + LOG_ERROR(p_ctx, "RTSP: Failed to add MIME type to URI"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + free(full_mime_type); + + if (!vc_uri_add_query(t_module->reader_uri, SAMPLE_RATE_NAME, sample_rate)) + { + LOG_ERROR(p_ctx, "RTSP: Failed to add sample rate to URI"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + + /* Optional channels specifier */ + channels = rtsp_parse_extract_ws(&ptr); + if (*channels) + { + if (!vc_uri_add_query(t_module->reader_uri, CHANNELS_NAME, channels)) + { + LOG_ERROR(p_ctx, "RTSP: Failed to add channels to URI"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Parse an fmtp attribute and store values in the related track. + * + * @param p_ctx The RTSP reader context. + * @param track The track relating to the fmtp. + * @param attribute The fmtp attribute value. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_parse_fmtp_attribute( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track, + char *attribute ) +{ + VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; + char *ptr = attribute; + char *payload_type; + + /* fmtp attribute format: + * + * The payload type must match the first one in the media field, parameters + * are semi-colon separated and may have additional whitespace around them. */ + + payload_type = rtsp_parse_extract_ws(&ptr); + if (strcmp(payload_type, t_module->payload_type)) + { + /* Ignore any unsupported secondary payload type attributes */ + LOG_DEBUG(p_ctx, "RTSP: Secondary payload type attribute - not supported"); + return VC_CONTAINER_SUCCESS; + } + + while (*ptr) + { + char *name; + char *value; + + /* Only add the parameter if the name was not empty. This avoids problems with + * strings like ";;", ";" or ";=value;" */ + if (rtsp_parse_extract_parameter(&ptr, &name, &value)) + { + if (!vc_uri_add_query(t_module->reader_uri, name, value)) + { + if (value) + LOG_ERROR(p_ctx, "RTSP: Failed to add <%s>=<%s> query to URI", name, value); + else + LOG_ERROR(p_ctx, "RTSP: Failed to add <%s> query to URI", name); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + } + } + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Merge base URI and relative URI strings into a merged URI string. + * Always creates a new string, even if the relative URI is actually absolute. + * + * @param p_ctx The RTSP reader context. + * @param base_uri_str The base URI string. + * @param relative_uri_str The relative URI string. + * @param p_merged_uri_str Pointer to where to put the merged string pointer. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_merge_uris( VC_CONTAINER_T *p_ctx, + const char *base_uri_str, + const char *relative_uri_str, + char **p_merged_uri_str) +{ + VC_URI_PARTS_T *base_uri = NULL; + VC_URI_PARTS_T *relative_uri = NULL; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + uint32_t merged_size; + + *p_merged_uri_str = NULL; + relative_uri = vc_uri_create(); + if (!relative_uri) goto tidy_up; + if (!vc_uri_parse(relative_uri, relative_uri_str)) + { + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + goto tidy_up; + } + + if (vc_uri_scheme(relative_uri) != NULL) + { + /* URI is absolute, not relative, so return it as the merged URI */ + size_t len = strlen(relative_uri_str); + + *p_merged_uri_str = (char *)malloc(len + 1); + if (!*p_merged_uri_str) goto tidy_up; + + strncpy(*p_merged_uri_str, relative_uri_str, len); + status = VC_CONTAINER_SUCCESS; + goto tidy_up; + } + + base_uri = vc_uri_create(); + if (!base_uri) goto tidy_up; + if (!vc_uri_parse(base_uri, base_uri_str)) + { + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + goto tidy_up; + } + + /* Build up merged URI in relative_uri, using base_uri as necessary */ + if (!vc_uri_merge(base_uri, relative_uri)) goto tidy_up; + + merged_size = vc_uri_build(relative_uri, NULL, 0) + 1; + *p_merged_uri_str = (char *)malloc(merged_size); + if (!*p_merged_uri_str) goto tidy_up; + + vc_uri_build(relative_uri, *p_merged_uri_str, merged_size); + + status = VC_CONTAINER_SUCCESS; + +tidy_up: + if (base_uri) vc_uri_release(base_uri); + if (relative_uri) vc_uri_release(relative_uri); + if (status != VC_CONTAINER_SUCCESS) + LOG_ERROR(p_ctx, "RTSP: Error merging URIs: %d", (int)status); + return status; +} + +/**************************************************************************//** + * Parse a control attribute and store it as an absolute URI. + * + * @param p_ctx The RTSP reader context. + * @param attribute The control attribute value. + * @param base_uri_str The base URI string. + * @param p_control_uri_str Pointer to where to put the control string pointer. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_parse_control_attribute( VC_CONTAINER_T *p_ctx, + const char *attribute, + const char *base_uri_str, + char **p_control_uri_str) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + /* control attribute format: + * + * The control URI is either absolute or relative to the base URI. If the + * control URI is just an asterisk, the control URI matches the base URI. */ + + if (!*attribute || strcmp(attribute, "*") == 0) + { + size_t len = strlen(base_uri_str); + + *p_control_uri_str = (char *)malloc(len + 1); + if (!*p_control_uri_str) + { + LOG_ERROR(p_ctx, "RTSP: Failed to allocate control URI"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + strncpy(*p_control_uri_str, base_uri_str, len); + } else { + status = rtsp_merge_uris(p_ctx, base_uri_str, attribute, p_control_uri_str); + } + + return status; +} + +/**************************************************************************//** + * Open a reader for the track using the URI that has been generated. + * + * @param p_ctx The RTSP reader context. + * @param t_module The track module for which a reader is needed. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_open_track_reader( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t uri_buffer_size; + char *uri_buffer; + + uri_buffer_size = vc_uri_build(t_module->reader_uri, NULL, 0) + 1; + uri_buffer = (char *)malloc(uri_buffer_size); + if (!uri_buffer) + { + LOG_ERROR(p_ctx, "RTSP: Failed to build RTP URI"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + vc_uri_build(t_module->reader_uri, uri_buffer, uri_buffer_size); + + t_module->reader = vc_container_open_reader(uri_buffer, &status, NULL, NULL); + free(uri_buffer); + + return status; +} + +/**************************************************************************//** + * Open a reader for the track using the network URI that has been generated. + * + * @param p_ctx The RTSP reader context. + * @param t_module The track module for which a reader is needed. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_open_network_reader( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + char port[PORT_BUFFER_SIZE] = {0}; + + if (!t_module->rtp_port) + { + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + t_module->rtp_port = module->next_rtp_port; + if (t_module->rtp_port > LAST_DYNAMIC_PORT) + { + LOG_ERROR(p_ctx, "RTSP: Out of dynamic ports"); + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + } + + module->next_rtp_port += 2; + } + + snprintf(port, sizeof(port)-1, "%hu", t_module->rtp_port); + if (!vc_uri_set_port(t_module->reader_uri, port)) + { + LOG_ERROR(p_ctx, "RTSP: Failed to set track reader URI port"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + + return rtsp_open_track_reader(p_ctx, t_module); +} + +/**************************************************************************//** + * Open a reader for the track using the file URI that has been generated. + * + * @param p_ctx The RTSP reader context. + * @param t_module The track module for which a reader is needed. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_open_file_reader( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + VC_CONTAINER_STATUS_T status; + VC_URI_PARTS_T *rtsp_uri = NULL; + const char *rtsp_path; + int len; + char *new_path = NULL; + char *extension; + + /* Use the RTSP URI's path, with the extension changed to ".t.pkt" + * where is the zero-based media item number. */ + + rtsp_uri = vc_uri_create(); + if (!rtsp_uri) + { + LOG_ERROR(p_ctx, "RTSP: Failed to create RTSP URI"); + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto tidy_up; + } + + if (!vc_uri_parse(rtsp_uri, p_ctx->priv->io->uri)) + { + LOG_ERROR(p_ctx, "RTSP: Failed to parse RTSP URI <%s>", p_ctx->priv->io->uri); + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + goto tidy_up; + } + + rtsp_path = vc_uri_path(rtsp_uri); + if (!rtsp_path || !*rtsp_path) + { + LOG_ERROR(p_ctx, "RTSP: RTSP URI path missing <%s>", p_ctx->priv->io->uri); + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + goto tidy_up; + } + + len = strlen(rtsp_path); + new_path = (char *)calloc(1, len + RTP_PATH_EXTRA + 1); + if (!rtsp_uri) + { + LOG_ERROR(p_ctx, "RTSP: Failed to create buffer for RTP path"); + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto tidy_up; + } + + strncpy(new_path, rtsp_path, len); + extension = strrchr(new_path, '.'); /* Find extension, to replace it */ + if (!extension) + extension = new_path + strlen(new_path); /* No extension, so append instead */ + + snprintf(extension, len + RTP_PATH_EXTRA - (extension - new_path), + RTP_PATH_EXTENSION_FORMAT, p_ctx->priv->module->media_item); + if (!vc_uri_set_path(t_module->reader_uri, new_path)) + { + LOG_ERROR(p_ctx, "RTSP: Failed to store RTP path <%s>", new_path); + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto tidy_up; + } + + free(new_path); + vc_uri_release(rtsp_uri); + + return rtsp_open_track_reader(p_ctx, t_module); + +tidy_up: + if (new_path) free(new_path); + if (rtsp_uri) vc_uri_release(rtsp_uri); + return status; +} + +/**************************************************************************//** + * Copy track information from the encapsulated track reader's track to the + * RTSP track. + * + * @param p_ctx The RTSP reader context. + * @param track The RTSP track requiring its information to be filled in. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_copy_track_data_from_reader( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track ) +{ + VC_CONTAINER_T *reader = track->priv->module->reader; + VC_CONTAINER_ES_FORMAT_T *src_format, *dst_format; + + if (reader->tracks_num != 1) + { + LOG_ERROR(p_ctx, "RTSP: Expected track reader to have one track, has %d", reader->tracks_num); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + if (reader->tracks[0]->meta_num) + { + LOG_DEBUG(p_ctx, "RTSP: Track reader has meta data - not supported"); + } + + src_format = reader->tracks[0]->format; + dst_format = track->format; + + /* Copy fields individually to avoid problems with pointers (type and extradata). */ + dst_format->es_type = src_format->es_type; + dst_format->codec = src_format->codec; + dst_format->codec_variant = src_format->codec_variant; + *dst_format->type = *src_format->type; + dst_format->bitrate = src_format->bitrate; + memcpy(dst_format->language, src_format->language, sizeof(dst_format->language)); + dst_format->group_id = src_format->group_id; + dst_format->flags = src_format->flags; + + if (src_format->extradata) + { + VC_CONTAINER_STATUS_T status; + uint32_t extradata_size = src_format->extradata_size; + + status = vc_container_track_allocate_extradata(p_ctx, track, extradata_size); + if (status != VC_CONTAINER_SUCCESS) + return status; + + memcpy(dst_format->extradata, src_format->extradata, extradata_size); + dst_format->extradata_size = extradata_size; + } + + track->is_enabled = reader->tracks[0]->is_enabled; + + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Finalise the creation of the RTSP track by opening its reader and filling in + * the track information. + * + * @param p_ctx The RTSP reader context. + * @param track The RTSP track being finalised. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_complete_track( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + if (!t_module->control_uri) + { + LOG_ERROR(p_ctx, "RTSP: Track control URI is missing"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + + if (module->uri_has_network_info) + { + int ii; + + if (!vc_uri_set_host(t_module->reader_uri, "")) + { + LOG_ERROR(p_ctx, "RTSP: Failed to set track reader URI host"); + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + } + + status = rtsp_open_network_reader(p_ctx, t_module); + + for (ii = 0; status == VC_CONTAINER_ERROR_URI_OPEN_FAILED && ii < DYNAMIC_PORT_ATTEMPTS_MAX; ii++) + { + /* Reset port to pick up next dynamic port */ + t_module->rtp_port = 0; + status = rtsp_open_network_reader(p_ctx, t_module); + } + + /* Change I/O to non-blocking, so that tracks can be polled */ + if (status == VC_CONTAINER_SUCCESS) + status = vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 0); + /* Set a large read buffer, to avoid dropping bursts of large packets (e.g. hi-def video) */ + if (status == VC_CONTAINER_SUCCESS) + status = vc_container_control(t_module->reader, VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE, UDP_READ_BUFFER_SIZE); + } else { + status = rtsp_open_file_reader(p_ctx, t_module); + } + + vc_uri_release(t_module->reader_uri); + t_module->reader_uri = NULL; + + if (status == VC_CONTAINER_SUCCESS) + status = rtsp_copy_track_data_from_reader(p_ctx, track); + + return status; +} + +/**************************************************************************//** + * Returns whether the given character is an attribute name delimiter or not. + * + * @param char_to_test The character under test. + * @return True if the character is an attribute name delimiter, false if not. + */ +static int attribute_name_delimiter_fn(int char_to_test) +{ + return (char_to_test == ':'); +} + +/**************************************************************************//** + * Create RTSP tracks from media fields in SDP formatted data. + * + * @param p_ctx The RTSP reader context. + * @param sdp_buffer The SDP data. + * @param base_uri The RTSP base URI. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_create_tracks_from_sdp( VC_CONTAINER_T *p_ctx, + char *sdp_buffer, + char *base_uri ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_TRACK_T *track = NULL; + char *session_base_uri = base_uri; + char *ptr; + char *next_line_ptr; + char *attribute; + + for (ptr = sdp_buffer; status == VC_CONTAINER_SUCCESS && *ptr; ptr = next_line_ptr) + { + /* Find end of line */ + char field = *ptr; + + next_line_ptr = ptr; + while (*next_line_ptr && *next_line_ptr != '\n') + next_line_ptr++; + + /* Terminate line */ + if (*next_line_ptr) + *next_line_ptr++ = '\0'; + + /* The format of the line has to be "=" where is a single + * character. Ignore anything else. */ + if (ptr[1] != '=') + continue; + ptr = rtsp_trim(ptr + 2); + + switch (field) + { + case 'm': + /* Start of media item */ + if (track) + { + /* Finish previous track */ + status = rtsp_complete_track(p_ctx, track); + track = NULL; + p_ctx->priv->module->media_item++; + if (status != VC_CONTAINER_SUCCESS) + break; + } + + status = rtsp_create_track_for_media_field(p_ctx, ptr, &track); + break; + case 'a': /* Attribute (either session or media level) */ + /* Attributes of the form "a=:" */ + attribute = rtsp_parse_extract(&ptr, attribute_name_delimiter_fn, NULL); + + if (track) + { + /* Media level attribute */ + + /* Look for known attributes */ + if (strcmp(attribute, "rtpmap") == 0) + status = rtsp_parse_rtpmap_attribute(p_ctx, track, ptr); + else if (strcmp(attribute, "fmtp") == 0) + status = rtsp_parse_fmtp_attribute(p_ctx, track, ptr); + else if (strcmp(attribute, "control") == 0) + { + char **track_control_uri = &track->priv->module->control_uri; + + if (*track_control_uri) + { + free(*track_control_uri); + *track_control_uri = NULL; + } + status = rtsp_parse_control_attribute(p_ctx, ptr, session_base_uri, track_control_uri); + } + /* Any other attributes are ignored */ + } else { + /* Session level attribute */ + if (strcmp(attribute, "control") == 0) + { + /* Only need to change the session_base_uri if it differs from the base URI */ + ptr = rtsp_trim(ptr); + if (session_base_uri != base_uri) + { + free(session_base_uri); + session_base_uri = base_uri; + } + if (strcmp(ptr, base_uri) != 0) + status = rtsp_parse_control_attribute(p_ctx, ptr, base_uri, &session_base_uri); + } + } + break; + default: /* Ignore any other field names */ + ; + } + } + + if (session_base_uri && session_base_uri != base_uri) + free(session_base_uri); + + /* Having no media fields is an error, since there will be nothing to play back */ + if (status == VC_CONTAINER_SUCCESS) + { + if (!p_ctx->tracks_num) + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + else if (track) + { + /* Finish final track */ + status = rtsp_complete_track(p_ctx, track); + p_ctx->priv->module->media_item++; + } + } + + return status; +} + +/**************************************************************************//** + * Create RTSP tracks from the response to a DESCRIBE request. + * The response must have already been filled into the comms buffer and header + * list. + * + * @param p_ctx The RTSP reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_create_tracks_from_response( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINERS_LIST_T *header_list = p_ctx->priv->module->header_list; + RTSP_HEADER_T header; + char *base_uri; + char *content; + + header.name = CONTENT_PSEUDOHEADER_NAME; + if (!vc_containers_list_find_entry(header_list, &header)) + { + LOG_ERROR(p_ctx, "RTSP: Content missing"); + return VC_CONTAINER_ERROR_FORMAT_INVALID; + } + content = header.value; + + /* The control URI may be relative to a base URI which is the first of these + * that is available: + * 1. Content-Base header + * 2. Content-Location header + * 3. Request URI + */ + header.name = CONTENT_BASE_NAME; + if (vc_containers_list_find_entry(header_list, &header)) + base_uri = header.value; + else { + header.name = CONTENT_LOCATION_NAME; + if (vc_containers_list_find_entry(header_list, &header)) + base_uri = header.value; + else + base_uri = p_ctx->priv->io->uri; + } + + return rtsp_create_tracks_from_sdp(p_ctx, content, base_uri); +} + +/**************************************************************************//** + * Header comparison function. + * Compare two header structures and return whether the first is less than, + * equal to or greater than the second. + * + * @param first The first structure to be compared. + * @param second The second structure to be compared. + * @return Negative if first is less than second, positive if first is greater + * and zero if they are equal. + */ +static int rtsp_header_comparator(const RTSP_HEADER_T *first, const RTSP_HEADER_T *second) +{ + return strcasecmp(first->name, second->name); +} + +/**************************************************************************//** + * Make a DESCRIBE request to the server and create tracks from the response. + * + * @param p_ctx The RTSP reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_describe( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + + /* Send DESCRIBE request and get response */ + status = rtsp_send_describe_request(p_ctx); + if (status != VC_CONTAINER_SUCCESS) return status; + status = rtsp_read_response(p_ctx); + if (status != VC_CONTAINER_SUCCESS) return status; + + /* Create tracks from SDP content */ + status = rtsp_create_tracks_from_response(p_ctx); + + return status; +} + +/**************************************************************************//** + * Make a SETUP request to the server and get session from response. + * + * @param p_ctx The RTSP reader context. + * @param t_module The track module to be set up. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_setup( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + const char *session_header; + size_t session_header_len; + + status = rtsp_send_setup_request(p_ctx, t_module); + if (status != VC_CONTAINER_SUCCESS) return status; + status = rtsp_read_response(p_ctx); + if (status != VC_CONTAINER_SUCCESS) return status; + + session_header = rtsp_get_session_header(module->header_list); + session_header_len = strlen(session_header); + if (session_header_len > SESSION_HEADER_LENGTH_MAX) return VC_CONTAINER_ERROR_FORMAT_INVALID; + + t_module->session_header = (char *)malloc(session_header_len + 1); + if (!t_module->session_header) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + strncpy(t_module->session_header, session_header, session_header_len); + + return status; +} + +/**************************************************************************//** + * Make a SETUP request to the server and get session from response. + * + * @param p_ctx The RTSP reader context. + * @param t_module The track module to be set up. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_play( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_MODULE_T *t_module ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + + status = rtsp_send_play_request(p_ctx, t_module); + if (status != VC_CONTAINER_SUCCESS) return status; + status = rtsp_read_response(p_ctx); + if (status != VC_CONTAINER_SUCCESS) return status; + + rtsp_store_rtp_info(module->header_list, t_module); + + return status; +} + +/**************************************************************************//** + * Blocking read/skip data from a container. + * Can also be used to query information about the next block of data. + * + * @pre The container is set to non-blocking. + * @post The container is set to non-blocking. + * + * @param p_ctx The reader context. + * @param p_packet The container packet information, or NULL. + * @param flags The container read flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_blocking_track_read(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, + uint32_t flags) +{ + VC_CONTAINER_STATUS_T status; + + status = vc_container_read(p_ctx, p_packet, flags); + + /* The ..._ABORTED status corresponds to a timeout waiting for data */ + if (status == VC_CONTAINER_ERROR_ABORTED) + { + /* So switch to blocking temporarily to wait for some */ + (void)vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, VC_CONTAINER_READ_TIMEOUT_BLOCK); + status = vc_container_read(p_ctx, p_packet, flags); + (void)vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 0); + } + + return status; +} + +/**************************************************************************//** + * Update the cached packet info blocks for all tracks. + * If one or more of the tracks has data, set the current track to the one with + * the earliest decode timestamp. + * + * @pre The track readers must not block when data is requested from them. + * + * @param p_ctx The RTSP reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_update_track_info( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t tracks_num = p_ctx->tracks_num; + uint32_t track_idx; + int64_t earliest_dts = MAXIMUM_INT64; + VC_CONTAINER_TRACK_MODULE_T *earliest_track = NULL; + + /* Reset current track to unknown */ + p_ctx->priv->module->current_track = NULL; + + /* Collect each track's info and return the one with earliest timestamp. */ + for (track_idx = 0; track_idx < tracks_num; track_idx++) + { + VC_CONTAINER_TRACK_MODULE_T *t_module = p_ctx->tracks[track_idx]->priv->module; + VC_CONTAINER_PACKET_T *info = &t_module->info; + + /* If this track has no data available, request more */ + if (!info->size) + { + /* This is a non-blocking read, so status will be ..._ABORTED if nothing available */ + status = vc_container_read(t_module->reader, info, VC_CONTAINER_READ_FLAG_INFO); + /* Adjust track index to be the RTSP index instead of the RTP one */ + info->track = track_idx; + } + + if (status == VC_CONTAINER_SUCCESS) + { + if (info->dts < earliest_dts) + { + earliest_dts = info->dts; + earliest_track = t_module; + } + } + else if (status != VC_CONTAINER_ERROR_ABORTED) + { + /* Not a time-out failure, so abort */ + return status; + } + } + + p_ctx->priv->module->current_track = earliest_track; + + return VC_CONTAINER_SUCCESS; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ + +/**************************************************************************//** + * Read/skip data from the container. + * Can also be used to query information about the next block of data. + * + * @param p_ctx The reader context. + * @param p_packet The container packet information, or NULL. + * @param flags The container read flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, + uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_TRACK_MODULE_T *current_track = module->current_track; + VC_CONTAINER_PACKET_T *info; + + if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) + { + vc_container_assert(p_packet); + vc_container_assert(p_packet->track < p_ctx->tracks_num); + current_track = p_ctx->tracks[p_packet->track]->priv->module; + module->current_track = current_track; + + if (!current_track->info.size) + { + status = rtsp_blocking_track_read(current_track->reader, ¤t_track->info, VC_CONTAINER_READ_FLAG_INFO); + if (status != VC_CONTAINER_SUCCESS) + goto error; + } + } + else if (!current_track || !current_track->info.size) + { + status = rtsp_update_track_info(p_ctx); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + while (!module->current_track) + { + /* Check RTSP stream to see if it has closed */ + status = rtsp_read_response(p_ctx); + if (status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_ABORTED) + { + /* No data from any track yet, so keep checking */ + status = rtsp_update_track_info(p_ctx); + } + if (status != VC_CONTAINER_SUCCESS) + goto error; + } + + current_track = module->current_track; + } + + info = ¤t_track->info; + vc_container_assert(info->size); + + if (flags & VC_CONTAINER_READ_FLAG_INFO) + { + vc_container_assert(p_packet); + memcpy(p_packet, info, sizeof(*info)); + } else { + status = rtsp_blocking_track_read(current_track->reader, p_packet, flags); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + if (p_packet) + { + p_packet->track = info->track; + + if (flags & VC_CONTAINER_READ_FLAG_SKIP) + { + info->size = 0; + } else { + vc_container_assert(info->size >= p_packet->size); + info->size -= p_packet->size; + } + } else { + info->size = 0; + } + } + + if (p_packet) + { + /* Adjust timestamps to be relative to zero */ + if (!module->ts_base) + module->ts_base = p_packet->dts; + p_packet->dts -= module->ts_base; + p_packet->pts -= module->ts_base; + } + +error: + STREAM_STATUS(p_ctx) = status; + return status; +} + +/**************************************************************************//** + * Seek over data in the container. + * + * @param p_ctx The reader context. + * @param p_offset The seek offset. + * @param mode The seek mode. + * @param flags The seek flags. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_reader_seek( VC_CONTAINER_T *p_ctx, + int64_t *p_offset, + VC_CONTAINER_SEEK_MODE_T mode, + VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_PARAM_UNUSED(p_ctx); + VC_CONTAINER_PARAM_UNUSED(p_offset); + VC_CONTAINER_PARAM_UNUSED(mode); + VC_CONTAINER_PARAM_UNUSED(flags); + + /* RTSP is a non-seekable container */ + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; +} + +/**************************************************************************//** + * Close the container. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +static VC_CONTAINER_STATUS_T rtsp_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_MODULE_T *t_module = p_ctx->tracks[i]->priv->module; + + if (t_module->control_uri && t_module->session_header) + { + /* Send the teardown message and wait for a response, although it + * isn't important whether it was successful or not. */ + if (rtsp_send_teardown_request(p_ctx, t_module) == VC_CONTAINER_SUCCESS) + (void)rtsp_read_response(p_ctx); + } + + if (t_module->reader) + vc_container_close(t_module->reader); + if (t_module->reader_uri) + vc_uri_release(t_module->reader_uri); + if (t_module->control_uri) + free(t_module->control_uri); + if (t_module->session_header) + free(t_module->session_header); + vc_container_free_track(p_ctx, p_ctx->tracks[i]); /* Also need to close track's reader */ + } + p_ctx->tracks = NULL; + p_ctx->tracks_num = 0; + if (module) + { + if (module->comms_buffer) + free(module->comms_buffer); + if (module->header_list) + vc_containers_list_destroy(module->header_list); + free(module); + } + p_ctx->priv->module = 0; + return VC_CONTAINER_SUCCESS; +} + +/**************************************************************************//** + * Open the container. + * Uses the I/O URI and/or data to configure the container. + * + * @param p_ctx The reader context. + * @return The resulting status of the function. + */ +VC_CONTAINER_STATUS_T rtsp_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + uint32_t ii; + + /* Check the URI scheme looks valid */ + if (!vc_uri_scheme(p_ctx->priv->uri) || + (strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTSP_SCHEME) && + strcasecmp(vc_uri_scheme(p_ctx->priv->uri), RTSP_PKT_SCHEME))) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + if ((module = (VC_CONTAINER_MODULE_T *)malloc(sizeof(VC_CONTAINER_MODULE_T))) == NULL) + { + status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; + goto error; + } + + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks = module->tracks; + module->next_rtp_port = FIRST_DYNAMIC_PORT; + module->cseq_value = 0; + module->uri_has_network_info = + (strncasecmp(p_ctx->priv->io->uri, RTSP_NETWORK_URI_START, RTSP_NETWORK_URI_START_LENGTH) == 0); + module->comms_buffer = (char *)calloc(1, COMMS_BUFFER_SIZE+1); + if (!module->comms_buffer) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + + /* header_list will contain pointers into the response_buffer, so take care in re-use */ + module->header_list = vc_containers_list_create(HEADER_LIST_INITIAL_CAPACITY, sizeof(RTSP_HEADER_T), + (VC_CONTAINERS_LIST_COMPARATOR_T)rtsp_header_comparator); + if (!module->header_list) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + + status = rtsp_describe(p_ctx); + for (ii = 0; status == VC_CONTAINER_SUCCESS && ii < p_ctx->tracks_num; ii++) + status = rtsp_setup(p_ctx, p_ctx->tracks[ii]->priv->module); + for (ii = 0; status == VC_CONTAINER_SUCCESS && ii < p_ctx->tracks_num; ii++) + status = rtsp_play(p_ctx, p_ctx->tracks[ii]->priv->module); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + /* Set the RTSP stream to block briefly, to allow polling for closure as well as to avoid spinning CPU */ + vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, DATA_UNAVAILABLE_READ_TIMEOUT_MS); + + p_ctx->priv->pf_close = rtsp_reader_close; + p_ctx->priv->pf_read = rtsp_reader_read; + p_ctx->priv->pf_seek = rtsp_reader_seek; + + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) goto error; + return VC_CONTAINER_SUCCESS; + +error: + if(status == VC_CONTAINER_SUCCESS || status == VC_CONTAINER_ERROR_EOS) + status = VC_CONTAINER_ERROR_FORMAT_INVALID; + LOG_DEBUG(p_ctx, "error opening RTSP stream (%i)", status); + rtsp_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open rtsp_reader_open +#endif diff --git a/containers/rv9/CMakeLists.txt b/containers/rv9/CMakeLists.txt new file mode 100644 index 0000000..f65a7af --- /dev/null +++ b/containers/rv9/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_rv9 ${LIBRARY_TYPE} rv9_reader.c) + +target_link_libraries(reader_rv9 containers) + +install(TARGETS reader_rv9 DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/rv9/rv9_reader.c b/containers/rv9/rv9_reader.c new file mode 100644 index 0000000..cb4ca40 --- /dev/null +++ b/containers/rv9/rv9_reader.c @@ -0,0 +1,335 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +/****************************************************************************** +Defines. +******************************************************************************/ + +#define BI32(b) (((b)[0]<<24)|((b)[1]<<16)|((b)[2]<<8)|((b)[3])) +#define BI16(b) (((b)[0]<<8)|((b)[1])) + +#define FRAME_HEADER_LEN 20 +#define MAX_NUM_SEGMENTS 64 + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct { + uint32_t len; + uint32_t timestamp; + uint16_t sequence; + uint16_t flags; + uint32_t num_segments; + uint32_t seg_offset; +} RV9_FRAME_HEADER_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *track; + uint8_t mid_frame; + uint32_t frame_read; + uint32_t frame_len; + RV9_FRAME_HEADER_T hdr; + uint8_t data[FRAME_HEADER_LEN + (MAX_NUM_SEGMENTS<<3) + 1]; + uint32_t data_len; + uint8_t type; +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T rv9_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +static VC_CONTAINER_STATUS_T rv9_read_file_header(VC_CONTAINER_T *p_ctx, + VC_CONTAINER_TRACK_T *track) +{ + VC_CONTAINER_STATUS_T status; + VC_CONTAINER_FOURCC_T codec; + uint8_t dummy[12]; + uint32_t length; + + if(PEEK_BYTES(p_ctx, dummy, sizeof(dummy)) != sizeof(dummy)) return VC_CONTAINER_ERROR_EOS; + + length = BI32(dummy); + if(length < 12 || length > 1024) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + if(dummy[4] != 'V' || dummy[5] != 'I' || dummy[6] != 'D' || dummy[7] != 'O' || + dummy[8] != 'R' || dummy[9] != 'V' || dummy[11] != '0') + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + switch(dummy[10]) { + case '4': codec = VC_CONTAINER_CODEC_RV40; break; + case '3': codec = VC_CONTAINER_CODEC_RV30; break; + case '2': codec = VC_CONTAINER_CODEC_RV20; break; + case '1': codec = VC_CONTAINER_CODEC_RV10; break; + default: return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + } + + if (!track) + return VC_CONTAINER_SUCCESS; + + status = vc_container_track_allocate_extradata(p_ctx, track, length); + if(status != VC_CONTAINER_SUCCESS) return status; + + if(READ_BYTES(p_ctx, track->format->extradata, length) != length) return VC_CONTAINER_ERROR_EOS; + track->format->extradata_size = length; + + track->format->codec = codec; + return STREAM_STATUS(p_ctx); +} + +static VC_CONTAINER_STATUS_T rv9_read_frame_header(VC_CONTAINER_T *p_ctx) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t seg_offset = (uint32_t) -1; + uint8_t *buffer = module->data + FRAME_HEADER_LEN; + + if(READ_BYTES(p_ctx, module->data, FRAME_HEADER_LEN) != FRAME_HEADER_LEN) return VC_CONTAINER_ERROR_EOS; + module->data_len = FRAME_HEADER_LEN; + + module->hdr.len = BI32(module->data); + module->hdr.timestamp = BI32(module->data+4); + module->hdr.sequence = BI16(module->data+8); + module->hdr.flags = BI16(module->data+10); + module->hdr.num_segments = BI32(module->data+16); + + module->frame_len = FRAME_HEADER_LEN + (module->hdr.num_segments * 8) + module->hdr.len; + + // if we have space, we store up the segments in memory so we can tell the frame + // type, since most streams have their type byte as the first follow the segment information. + // if we don't have space, then we just don't know the frame type, so will not emit timestamp + // information as we don't know if it's reliable. + if(module->hdr.num_segments <= MAX_NUM_SEGMENTS) + { + uint32_t i; + + if(READ_BYTES(p_ctx, buffer, 8*module->hdr.num_segments) != 8*module->hdr.num_segments) return VC_CONTAINER_ERROR_EOS; + module->data_len += (module->hdr.num_segments * 8); + + for (i=0; ihdr.num_segments; i++) + { + uint32_t valid_seg; + uint32_t offset; + + valid_seg = BI32(buffer); + offset = BI32(buffer+4); + + if (valid_seg && seg_offset > offset) + seg_offset = offset; + + // this boolean field should have only 0 or 1 values + if(valid_seg > 1) return VC_CONTAINER_ERROR_FORMAT_INVALID; + + buffer += 8; + } + } + + if(seg_offset == 0) + { + if (READ_BYTES(p_ctx, buffer, 1) != 1) return VC_CONTAINER_ERROR_EOS; + module->data_len += 1; + + module->type = (*buffer >> 5) & 3; + } + else + module->type = (uint8_t) -1; + + return VC_CONTAINER_SUCCESS; +} + +static uint32_t rv9_get_frame_data(VC_CONTAINER_T *p_ctx, uint32_t len, uint8_t *dest) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t ret = 0; + + // we may have read some data before into the data array, so + // check whether we've copied all this data out first. + if(module->frame_read < module->data_len) + { + uint32_t copy = MIN(len, module->data_len - module->frame_read); + if(dest) + { + memcpy(dest, module->data + module->frame_read, copy); + dest += copy; + } + ret += copy; + len -= copy; + } + + // if there is still more to do, we need to access the IO to do this. + if(len > 0) + { + if(dest) + ret += READ_BYTES(p_ctx, dest, len); + else + ret += SKIP_BYTES(p_ctx, len); + } + + module->frame_read += ret; + return ret; +} + +/***************************************************************************** +Functions exported as part of the Container Module API +*****************************************************************************/ +static VC_CONTAINER_STATUS_T rv9_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *packet, + uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_STATUS_T status; + unsigned int size; + + if(!module->mid_frame) + { + if((status = rv9_read_frame_header(p_ctx)) != VC_CONTAINER_SUCCESS) return status; + + module->mid_frame = 1; + module->frame_read = 0; + } + + packet->size = module->frame_len; + packet->pts = module->type < 3 ? module->hdr.timestamp * 1000LL : VC_CONTAINER_TIME_UNKNOWN; + packet->dts = packet->pts; + packet->track = 0; + packet->flags = module->type < 2 ? VC_CONTAINER_PACKET_FLAG_KEYFRAME : 0; + if(module->frame_read == 0) + packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + { + size = rv9_get_frame_data(p_ctx, module->frame_len - module->frame_read, NULL); + if(module->frame_read == module->frame_len) + { + module->frame_read = 0; + module->mid_frame = 0; + } + return STREAM_STATUS(p_ctx); + } + + if(flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + size = MIN(module->frame_len - module->frame_read, packet->buffer_size); + size = rv9_get_frame_data(p_ctx, size, packet->data); + if(module->frame_read == module->frame_len) + { + module->frame_read = 0; + module->mid_frame = 0; + packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + } + packet->size = size; + + return size ? VC_CONTAINER_SUCCESS : STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rv9_reader_seek( VC_CONTAINER_T *p_ctx, + int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, + VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + VC_CONTAINER_PARAM_UNUSED(flags); + + if(*offset == 0LL && mode == VC_CONTAINER_SEEK_MODE_TIME) + { + SEEK(p_ctx, module->track->format->extradata_size); + module->mid_frame = 0; + module->frame_read = 0; + return STREAM_STATUS(p_ctx); + } + else + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T rv9_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + for(; p_ctx->tracks_num > 0; p_ctx->tracks_num--) + vc_container_free_track(p_ctx, p_ctx->tracks[p_ctx->tracks_num-1]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T rv9_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Check the file header */ + if(rv9_read_file_header(p_ctx, 0) != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks_num = 1; + p_ctx->tracks = &module->track; + p_ctx->tracks[0] = vc_container_allocate_track(p_ctx, 0); + if(!p_ctx->tracks[0]) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + p_ctx->tracks[0]->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + p_ctx->tracks[0]->format->codec = VC_CONTAINER_CODEC_RV40; + p_ctx->tracks[0]->is_enabled = true; + + if((status = rv9_read_file_header(p_ctx, p_ctx->tracks[0])) != VC_CONTAINER_SUCCESS) goto error; + + LOG_DEBUG(p_ctx, "using rv9 reader"); + + p_ctx->priv->pf_close = rv9_reader_close; + p_ctx->priv->pf_read = rv9_reader_read; + p_ctx->priv->pf_seek = rv9_reader_seek; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "rv9: error opening stream (%i)", status); + if(module) rv9_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function +********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open rv9_reader_open +#endif diff --git a/containers/simple/CMakeLists.txt b/containers/simple/CMakeLists.txt new file mode 100644 index 0000000..0fbdc11 --- /dev/null +++ b/containers/simple/CMakeLists.txt @@ -0,0 +1,18 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_simple ${LIBRARY_TYPE} simple_reader.c) + +target_link_libraries(reader_simple containers) + +install(TARGETS reader_simple DESTINATION ${VMCS_PLUGIN_DIR}) + +add_library(writer_simple ${LIBRARY_TYPE} simple_writer.c) + +target_link_libraries(writer_simple containers) + +install(TARGETS writer_simple DESTINATION ${VMCS_PLUGIN_DIR}) diff --git a/containers/simple/simple_common.h b/containers/simple/simple_common.h new file mode 100644 index 0000000..b75242c --- /dev/null +++ b/containers/simple/simple_common.h @@ -0,0 +1,42 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef SIMPLE_COMMON_H +#define SIMPLE_COMMON_H + +#define SIGNATURE_STRING "S1MPL3" +#define SIGNATURE_END_STRING "3LPM1S" + +/** List of configuration options supported in the header */ +#define CONFIG_VARIANT "VARIANT" +#define CONFIG_URI "URI" +#define CONFIG_CODEC_VARIANT "CODEC_VARIANT" +#define CONFIG_BITRATE "BITRATE" +#define CONFIG_UNFRAMED "UNFRAMED" +#define CONFIG_VIDEO_CROP "VIDEO_CROP" +#define CONFIG_VIDEO_ASPECT "VIDEO_ASPECT" + +#endif /* SIMPLE_COMMON_H */ diff --git a/containers/simple/simple_reader.c b/containers/simple/simple_reader.c new file mode 100644 index 0000000..f0ef55a --- /dev/null +++ b/containers/simple/simple_reader.c @@ -0,0 +1,599 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +#include "simple_common.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define MAX_LINE_SIZE 512 +#define LINE_PADDING 3 /* 2 for newline + 1 for null */ + +#define MAX_TRACKS 4 +#define MAX_HEADER_LINES 512 + +typedef enum SIMPLE_VARIANT_T +{ + VARIANT_DEFAULT = 0, + VARIANT_MMAL, + VARIANT_OMX +} SIMPLE_VARIANT_T; + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct SIMPLE_PACKET_STATE_T +{ + unsigned int track_num; + unsigned int flags; + + uint64_t metadata_offset; /* Offset in metadata stream */ + uint32_t data_size; /* Size of current data packet */ + uint32_t data_left; /* Data left to read in current packet */ + + int64_t pts; + +} SIMPLE_PACKET_STATE_T; + +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + SIMPLE_PACKET_STATE_T *state; + SIMPLE_PACKET_STATE_T local_state; + + VC_CONTAINER_IO_T *io; + uint64_t data_offset; /* Current offset in data stream */ + char uri[MAX_LINE_SIZE+1]; + + SIMPLE_VARIANT_T variant; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + VC_CONTAINER_TRACK_T *tracks[MAX_TRACKS]; + + char line[MAX_LINE_SIZE + LINE_PADDING]; + + int64_t metadata_offset; + + /* Shared packet state. This is used when the tracks are in sync, + * and for the track at the earliest position in the file when they are + * not in sync */ + SIMPLE_PACKET_STATE_T state; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T simple_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ +static VC_CONTAINER_STATUS_T simple_read_line( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + unsigned int i, bytes = PEEK_BYTES(ctx, module->line, sizeof(module->line)-1); + + if (!bytes) + return VC_CONTAINER_ERROR_EOS; + + /* Find new-line marker */ + for (i = 0; i < bytes; i++) + if (module->line[i] == '\n') + break; + + /* Bail out if line is bigger than the maximum allowed */ + if (i == sizeof(module->line)-1) + { + LOG_ERROR(ctx, "line too big"); + return VC_CONTAINER_ERROR_CORRUPTED; + } + + if (i < bytes) + { + module->line[i++] = 0; + if (i < bytes && module->line[i] == '\r') + i++; + } + module->line[i] = 0; /* Make sure the line is null terminated */ + + SKIP_BYTES(ctx, i); + return VC_CONTAINER_SUCCESS; +} + +static VC_CONTAINER_STATUS_T simple_read_header( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + VC_CONTAINER_TRACK_T *track = NULL; + VC_CONTAINER_FOURCC_T fourcc; + int matches, width, height, channels, samplerate, bps, blockalign, value; + unsigned int lines = 1; + + /* Skip the signature */ + if (simple_read_line(ctx) != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_CORRUPTED; + + while (lines++ < MAX_HEADER_LINES && + simple_read_line(ctx) == VC_CONTAINER_SUCCESS) + { + /* Our exit condition is the end signature */ + if (!memcmp(module->line, SIGNATURE_END_STRING, sizeof(SIGNATURE_STRING)-1)) + { + if (track) ctx->tracks[ctx->tracks_num++] = track; + return VC_CONTAINER_SUCCESS; + } + + /* Start of track description */ + if (!memcmp(module->line, "TRACK ", sizeof("TRACK ")-1)) + { + /* Add track we were constructing */ + if (track) ctx->tracks[ctx->tracks_num++] = track; + track = NULL; + + if (ctx->tracks_num >= MAX_TRACKS) + { + LOG_ERROR(ctx, "too many tracks, ignoring: %s", module->line); + continue; + } + track = vc_container_allocate_track(ctx, sizeof(*track->priv->module)); + if (!track) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + track->is_enabled = true; + track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + + if ((matches = sscanf(module->line, + "TRACK video, %4c, %i, %i", + (char *)&fourcc, &width, &height)) > 0) + { + track->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; + track->format->codec = fourcc; + if (matches > 1) track->format->type->video.width = width; + if (matches > 2) track->format->type->video.height = height; + } + else if ((matches = sscanf(module->line, + "TRACK audio, %4c, %i, %i, %i, %i", + (char *)&fourcc, &channels, &samplerate, &bps, + &blockalign)) > 0) + { + track->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + track->format->codec = fourcc; + if (matches > 1) track->format->type->audio.channels = channels; + if (matches > 2) track->format->type->audio.sample_rate = samplerate; + if (matches > 3) track->format->type->audio.bits_per_sample = bps; + if (matches > 4) track->format->type->audio.block_align = blockalign; + } + if ((matches = sscanf(module->line, + "TRACK subpicture, %4c, %i", + (char *)&fourcc, &value)) > 0) + { + track->format->es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE; + track->format->codec = fourcc; + if (matches > 1) track->format->type->subpicture.encoding = value; + } + } + + if (!track) + continue; /* Nothing interesting */ + + /* VARIANT of the syntax */ + if (sscanf(module->line, CONFIG_VARIANT" %i", &value) == 1) + { + track->priv->module->variant = value; + LOG_FORMAT(ctx, CONFIG_VARIANT": %i", value); + } + /* URI for elementary stream */ + else if (sscanf(module->line, CONFIG_URI" %s", track->priv->module->uri) == 1) + LOG_FORMAT(ctx, CONFIG_URI": %s", track->priv->module->uri); + /* COCDEC_VARIANT of elementary stream */ + else if (sscanf(module->line, CONFIG_CODEC_VARIANT" %4c", (char *)&fourcc) == 1) + { + track->format->codec_variant = fourcc; + LOG_FORMAT(ctx, CONFIG_CODEC_VARIANT": %4.4s", (char *)&fourcc); + } + /* BITRATE of elementary stream */ + else if (sscanf(module->line, CONFIG_BITRATE" %i", &value) == 1) + { + track->format->bitrate = value; + LOG_FORMAT(ctx, CONFIG_BITRATE": %i", value); + } + /* UNFRAMED elementary stream */ + else if (!memcmp(module->line, CONFIG_UNFRAMED, sizeof(CONFIG_UNFRAMED)-1)) + { + track->format->flags &= ~VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; + LOG_FORMAT(ctx, CONFIG_UNFRAMED); + } + /* VIDEO_CROP information */ + else if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO && + sscanf(module->line, CONFIG_VIDEO_CROP" %i, %i", &width, &height) == 2) + { + track->format->type->video.visible_width = width; + track->format->type->video.visible_height = height; + LOG_FORMAT(ctx, CONFIG_VIDEO_CROP": %i, %i", width, height); + } + /* VIDEO_ASPECT information */ + else if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO && + sscanf(module->line, CONFIG_VIDEO_ASPECT" %i, %i", &width, &height) == 2) + { + track->format->type->video.par_num = width; + track->format->type->video.par_den = height; + LOG_FORMAT(ctx, CONFIG_VIDEO_ASPECT": %i, %i", width, height); + } + } + + if (track) vc_container_free_track(ctx, track); + return VC_CONTAINER_ERROR_CORRUPTED; +} + +static uint32_t simple_convert_packet_flags(VC_CONTAINER_T *ctx, + unsigned int track_num, uint32_t flags) +{ + typedef struct { uint32_t from; uint32_t to; } convert_from_t; + const convert_from_t convert_from_mmal[] = + { {1<<1, VC_CONTAINER_PACKET_FLAG_FRAME_START}, + {1<<2, VC_CONTAINER_PACKET_FLAG_FRAME_END}, + {1<<3, VC_CONTAINER_PACKET_FLAG_KEYFRAME}, + {1<<4, VC_CONTAINER_PACKET_FLAG_DISCONTINUITY}, + {1<<5, VC_CONTAINER_PACKET_FLAG_CONFIG}, + {1<<6, VC_CONTAINER_PACKET_FLAG_ENCRYPTED}, + {0, 0} }; + const convert_from_t convert_from_omx[] = + { {0x10, VC_CONTAINER_PACKET_FLAG_FRAME_END}, + {0x20, VC_CONTAINER_PACKET_FLAG_KEYFRAME}, + {0x80, VC_CONTAINER_PACKET_FLAG_CONFIG}, + {0, 0} }; + const convert_from_t *convert_from = NULL; + int i; + + switch (ctx->tracks[track_num]->priv->module->variant) + { + case VARIANT_MMAL: convert_from = convert_from_mmal; break; + case VARIANT_OMX: convert_from = convert_from_omx; break; + default: break; + } + + if (convert_from) + { + uint32_t new_flags = 0; + for (i = 0; convert_from[i].from; i++) + if (convert_from[i].from & flags) + new_flags |= convert_from[i].to; + return new_flags; + } + + return flags; +} + +static int64_t simple_convert_packet_pts(VC_CONTAINER_T *ctx, + unsigned int track_num, int64_t pts, uint32_t flags) +{ + if (ctx->tracks[track_num]->priv->module->variant == VARIANT_OMX && + flags & 0x100) + return VC_CONTAINER_TIME_UNKNOWN; + + return pts; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T simple_reader_read( VC_CONTAINER_T *ctx, + VC_CONTAINER_PACKET_T *packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + VC_CONTAINER_TRACK_MODULE_T *track_module; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + SIMPLE_PACKET_STATE_T *state; + + /* If a specific track has been selected, use the track packet state */ + if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) + state = ctx->tracks[packet->track]->priv->module->state; + else + state = &module->state; + + /* Switch to the next packet when the current one is empty */ + if (!state->data_left) + { + unsigned int track_num, size; + int64_t pts; + int flags; + + SEEK(ctx, state->metadata_offset); + status = simple_read_line(ctx); + if (status != VC_CONTAINER_SUCCESS) + return status; + + if (sscanf(module->line, "%u %u %"PRIi64" %i", + &track_num, &size, &pts, &flags) != 4 && + (track_num = 0, sscanf(module->line, "%u %"PRIi64" %i", + &size, &pts, &flags)) != 3) + { + LOG_ERROR(ctx, "invalid metadata: %s", module->line); + return VC_CONTAINER_ERROR_CORRUPTED; + } + state->metadata_offset = STREAM_POSITION(ctx); + + if (track_num >= ctx->tracks_num) + { + LOG_DEBUG(ctx, "skipping %i bytes for track %d/%d", + size, track_num, ctx->tracks_num); + return VC_CONTAINER_ERROR_CONTINUE; + } + + /* If we are reading from the global state (i.e. normal read or forced + read from the track on the global state), and the track we found is + not on the global state, reconnect the two */ + if (state == &module->state && + ctx->tracks[track_num]->priv->module->state != &module->state) + { + LOG_DEBUG(ctx, "reconnect track %u to the global state", track_num); + ctx->tracks[track_num]->priv->module->state = &module->state; + module->state = ctx->tracks[track_num]->priv->module->local_state; + return VC_CONTAINER_ERROR_CONTINUE; + } + + state->data_size = state->data_left = size; + state->track_num = track_num; + state->flags = simple_convert_packet_flags(ctx, track_num, flags); + state->pts = simple_convert_packet_pts(ctx, track_num, pts, flags); + + /* Discard empty packets */ + if (!state->data_size && !state->flags) + return VC_CONTAINER_ERROR_CONTINUE; + } + + /* If there is data from another track skip past it */ + if ((flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) && + state->track_num != packet->track) + { + LOG_DEBUG(ctx, "skipping track %d/%d as we are ignoring it", + state->track_num, ctx->tracks_num); + + track_module = ctx->tracks[packet->track]->priv->module; + + /* Handle disconnection from global state */ + if (state == &module->state && + ctx->tracks[state->track_num]->priv->module->state == &module->state) + { + /* Make a copy of the global state */ + LOG_DEBUG(ctx, "using local state on track %d", packet->track); + track_module->local_state = module->state; + track_module->state = &track_module->local_state; + } + + track_module->state->data_left = 0; + return VC_CONTAINER_ERROR_CONTINUE; + } + + /* + * From this point we know we have the packet which was requested + */ + + /* !!!! If we aren't in the right position in the file go there now. */ + + track_module = ctx->tracks[state->track_num]->priv->module; + packet->track = state->track_num; + packet->size = state->data_left; + packet->frame_size = (state->flags & VC_CONTAINER_PACKET_FLAG_FRAME) ? + state->data_size : 0; + packet->flags = state->flags; + packet->pts = state->pts; + packet->dts = VC_CONTAINER_TIME_UNKNOWN; + if (state->data_left != state->data_size) + packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_START; + + if (flags & VC_CONTAINER_READ_FLAG_SKIP) + { + track_module->data_offset += state->data_left; + state->data_left = 0; + return VC_CONTAINER_SUCCESS; + } + + if (flags & VC_CONTAINER_READ_FLAG_INFO) + { + return VC_CONTAINER_SUCCESS; + } + + /* Now try to read data into buffer */ + vc_container_io_seek(track_module->io, track_module->data_offset); + + packet->size = vc_container_io_read(track_module->io, packet->data, + MIN(packet->buffer_size, state->data_left)); + state->data_left -= packet->size; + track_module->data_offset += packet->size; + + if (state->data_left) + packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; + + return track_module->io->status; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T simple_reader_seek( VC_CONTAINER_T *ctx, int64_t *offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_PARAM_UNUSED(ctx); + VC_CONTAINER_PARAM_UNUSED(offset); + VC_CONTAINER_PARAM_UNUSED(mode); + VC_CONTAINER_PARAM_UNUSED(flags); + return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T simple_reader_close( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + + for (; ctx->tracks_num > 0; ctx->tracks_num--) + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[ctx->tracks_num-1]; + if (track->priv->module->io) + vc_container_io_close(track->priv->module->io); + vc_container_free_track(ctx, track); + } + + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T simple_reader_open( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + uint8_t h[sizeof(SIGNATURE_STRING)]; + unsigned int i; + + /* Check for the signature */ + if (PEEK_BYTES(ctx, h, sizeof(h)) != sizeof(h) || + memcmp(h, SIGNATURE_STRING, sizeof(SIGNATURE_STRING)-1)) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + LOG_DEBUG(ctx, "using simple reader"); + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if (!module) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + memset(module, 0, sizeof(*module)); + ctx->priv->module = module; + ctx->tracks = module->tracks; + + status = simple_read_header(ctx); + if (status != VC_CONTAINER_SUCCESS) + goto error; + + /* Open all the elementary streams */ + for (i = 0; i < ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[i]; + char *uri; + + track->priv->module->io = vc_container_io_open(track->priv->module->uri, + VC_CONTAINER_IO_MODE_READ, &status); + + /* URI might be relative to the path of the metadata file so + * try again with that new path */ + if (!track->priv->module->io && + (uri = malloc(strlen(ctx->priv->io->uri) + + strlen(track->priv->module->uri) + 1)) != NULL) + { + char *end; + + strcpy(uri, ctx->priv->io->uri); + + /* Find the last directory separator */ + for (end = uri + strlen(ctx->priv->io->uri) + 1; end != uri; end--) + if (*(end-1) == '/' || *(end-1) == '\\') + break; + strcpy(end, track->priv->module->uri); + + track->priv->module->io = vc_container_io_open(uri, + VC_CONTAINER_IO_MODE_READ, &status); + if (!track->priv->module->io) + LOG_ERROR(ctx, "could not open elementary stream: %s", uri); + free(uri); + } + if (!track->priv->module->io) + { + LOG_ERROR(ctx, "could not open elementary stream: %s", + track->priv->module->uri); + goto error; + } + } + + /* + * We now have all the information we really need to start playing the stream + */ + + module->metadata_offset = STREAM_POSITION(ctx); + + /* Initialise state for all tracks */ + module->state.metadata_offset = module->metadata_offset; + for (i = 0; i < ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[i]; + track->priv->module->state = &module->state; + } + + /* Look for the codec configuration data for each track so + * we can store it in the track format */ + for (i = 0; i < ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[i]; + VC_CONTAINER_PACKET_T packet; + packet.track = i; + status = VC_CONTAINER_ERROR_CONTINUE; + + while (status == VC_CONTAINER_ERROR_CONTINUE) + status = simple_reader_read(ctx, &packet, + VC_CONTAINER_READ_FLAG_INFO | VC_CONTAINER_READ_FLAG_FORCE_TRACK); + if (status != VC_CONTAINER_SUCCESS) + continue; + + status = vc_container_track_allocate_extradata(ctx, track, packet.size); + if (status != VC_CONTAINER_SUCCESS) + continue; + + packet.data = track->format->extradata; + packet.buffer_size = packet.size; + packet.size = 0; + status = simple_reader_read(ctx, &packet, + VC_CONTAINER_READ_FLAG_FORCE_TRACK); + if (status != VC_CONTAINER_SUCCESS) + continue; + + track->format->extradata_size = packet.size; + } + + ctx->priv->pf_close = simple_reader_close; + ctx->priv->pf_read = simple_reader_read; + ctx->priv->pf_seek = simple_reader_seek; + return VC_CONTAINER_SUCCESS; + + error: + LOG_ERROR(ctx, "simple: error opening stream (%i)", status); + simple_reader_close(ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open simple_reader_open +#endif diff --git a/containers/simple/simple_writer.c b/containers/simple/simple_writer.c new file mode 100644 index 0000000..e0cf0f5 --- /dev/null +++ b/containers/simple/simple_writer.c @@ -0,0 +1,348 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include + +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" + +#include "simple_common.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define MAX_TRACKS 4 +#define MAX_LINE_SIZE 512 + +#define ES_SUFFIX "%s.%2.2i.%4.4s" +#define ES_SUFFIX_SIZE 8 + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_TRACK_MODULE_T +{ + VC_CONTAINER_IO_T *io; + char *uri; + + bool config_done; + +} VC_CONTAINER_TRACK_MODULE_T; + +typedef struct VC_CONTAINER_MODULE_T +{ + char line[MAX_LINE_SIZE + 1]; + + VC_CONTAINER_TRACK_T *tracks[MAX_TRACKS]; + bool header_done; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T simple_writer_open( VC_CONTAINER_T * ); +static VC_CONTAINER_STATUS_T simple_writer_write( VC_CONTAINER_T *ctx, + VC_CONTAINER_PACKET_T *packet ); + +/****************************************************************************** +Local Functions +******************************************************************************/ +static VC_CONTAINER_STATUS_T simple_write_line( VC_CONTAINER_T *ctx, + const char *format, ...) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + va_list args; + int result; + + va_start(args, format); + result = vsnprintf(module->line, sizeof(module->line), format, args); + va_end(args); + + if (result >= (int)sizeof(module->line)) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + WRITE_BYTES(ctx, module->line, result); + _WRITE_U8(ctx, '\n'); + return STREAM_STATUS(ctx); +} + +static VC_CONTAINER_STATUS_T simple_write_header( VC_CONTAINER_T *ctx ) +{ + unsigned int i; + + simple_write_line(ctx, SIGNATURE_STRING); + + for (i = 0; i < ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[i]; + + if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) + { + simple_write_line(ctx, "TRACK video, %4.4s, %i, %i", + (char *)&track->format->codec, + (int)track->format->type->video.width, + (int)track->format->type->video.height); + if ((track->format->type->video.visible_width && + track->format->type->video.visible_width != + track->format->type->video.width) || + (track->format->type->video.visible_height && + track->format->type->video.visible_height != + track->format->type->video.height)) + simple_write_line(ctx, CONFIG_VIDEO_CROP" %i, %i", + track->format->type->video.visible_width, + track->format->type->video.visible_height); + if (track->format->type->video.par_num && + track->format->type->video.par_den) + simple_write_line(ctx, CONFIG_VIDEO_ASPECT" %i, %i", + track->format->type->video.par_num, + track->format->type->video.par_den); + } + else if (track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + { + simple_write_line(ctx, "TRACK audio, %4.4s, %i, %i, %i, %i", + (char *)&track->format->codec, + (int)track->format->type->audio.channels, + (int)track->format->type->audio.sample_rate, + (int)track->format->type->audio.bits_per_sample, + (int)track->format->type->audio.block_align); + } + else if (track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) + { + simple_write_line(ctx, "TRACK subpicture, %4.4s, %i", + (char *)&track->format->codec, + (int)track->format->type->subpicture.encoding); + } + else + { + simple_write_line(ctx, "TRACK unknown, %4.4s", + (char *)&track->format->codec); + } + + simple_write_line(ctx, CONFIG_URI" %s", track->priv->module->io->uri); + if (track->format->codec_variant) + simple_write_line(ctx, CONFIG_CODEC_VARIANT" %4.4s", + (char *)&track->format->codec_variant); + if (track->format->bitrate) + simple_write_line(ctx, CONFIG_BITRATE" %i", track->format->bitrate); + if (!(track->format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED)) + simple_write_line(ctx, CONFIG_UNFRAMED); + } + + simple_write_line(ctx, SIGNATURE_END_STRING); + + ctx->priv->module->header_done = true; + return STREAM_STATUS(ctx); +} + +static VC_CONTAINER_STATUS_T simple_write_config( VC_CONTAINER_T *ctx, + unsigned int track_num, VC_CONTAINER_PACKET_T *pkt) +{ + VC_CONTAINER_TRACK_T *track = ctx->tracks[track_num]; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_PACKET_T packet; + + track->priv->module->config_done = true; + + if (track->format->extradata_size) + { + packet.size = track->format->extradata_size; + packet.data = track->format->extradata; + packet.track = track_num; + packet.pts = pkt ? pkt->pts : VC_CONTAINER_TIME_UNKNOWN; + packet.flags = 0; + packet.flags |= VC_CONTAINER_PACKET_FLAG_CONFIG; + + status = simple_writer_write(ctx, &packet); + } + + return status; +} + +static VC_CONTAINER_STATUS_T simple_write_add_track( VC_CONTAINER_T *ctx, + VC_CONTAINER_ES_FORMAT_T *format ) +{ + VC_CONTAINER_TRACK_T *track = NULL; + VC_CONTAINER_STATUS_T status; + const char *uri = vc_uri_path(ctx->priv->uri); + unsigned int uri_size = strlen(uri); + + /* Allocate and initialise track data */ + if (ctx->tracks_num >= MAX_TRACKS) + return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; + + ctx->tracks[ctx->tracks_num] = track = + vc_container_allocate_track(ctx, sizeof(VC_CONTAINER_TRACK_MODULE_T) + + uri_size + ES_SUFFIX_SIZE + 1); + if (!track) + return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + if (format->extradata_size) + { + status = vc_container_track_allocate_extradata(ctx, track, format->extradata_size); + if (status != VC_CONTAINER_SUCCESS) + goto error; + } + vc_container_format_copy(track->format, format, format->extradata_size); + + track->priv->module->uri = (char *)&track->priv->module[1]; + snprintf(track->priv->module->uri, uri_size + ES_SUFFIX_SIZE + 1, + ES_SUFFIX, uri, ctx->tracks_num, (char *)&track->format->codec); + + LOG_DEBUG(ctx, "opening elementary stream: %s", track->priv->module->uri); + track->priv->module->io = vc_container_io_open(track->priv->module->uri, + VC_CONTAINER_IO_MODE_WRITE, &status); + if (status != VC_CONTAINER_SUCCESS) + { + LOG_ERROR(ctx, "error opening elementary stream: %s", + track->priv->module->uri); + goto error; + } + + ctx->tracks_num++; + return VC_CONTAINER_SUCCESS; + + error: + if (track) + vc_container_free_track(ctx, track); + return status; +} + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ +static VC_CONTAINER_STATUS_T simple_writer_close( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_MODULE_T *module = ctx->priv->module; + for (; ctx->tracks_num > 0; ctx->tracks_num--) + { + vc_container_io_close(ctx->tracks[ctx->tracks_num-1]->priv->module->io); + vc_container_free_track(ctx, ctx->tracks[ctx->tracks_num-1]); + } + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T simple_writer_write( VC_CONTAINER_T *ctx, + VC_CONTAINER_PACKET_T *packet ) +{ + VC_CONTAINER_STATUS_T status; + + if (!ctx->priv->module->header_done) + { + status = simple_write_header(ctx); + if (status != VC_CONTAINER_SUCCESS) + return status; + } + + if (!ctx->tracks[packet->track]->priv->module->config_done) + { + status = simple_write_config(ctx, packet->track, packet); + if (status != VC_CONTAINER_SUCCESS) + return status; + } + + /* Write the metadata */ + status = simple_write_line(ctx, "%i %i %"PRIi64" 0x%x", + (int)packet->track, (int)packet->size, packet->pts, packet->flags); + if (status != VC_CONTAINER_SUCCESS) + return status; + + /* Write the elementary stream */ + vc_container_io_write(ctx->tracks[packet->track]->priv->module->io, + packet->data, packet->size); + + return STREAM_STATUS(ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T simple_writer_control( VC_CONTAINER_T *ctx, + VC_CONTAINER_CONTROL_T operation, va_list args ) +{ + VC_CONTAINER_ES_FORMAT_T *format; + + switch (operation) + { + case VC_CONTAINER_CONTROL_TRACK_ADD: + format = (VC_CONTAINER_ES_FORMAT_T *)va_arg(args, VC_CONTAINER_ES_FORMAT_T *); + return simple_write_add_track(ctx, format); + + case VC_CONTAINER_CONTROL_TRACK_ADD_DONE: + simple_write_header( ctx ); + return VC_CONTAINER_SUCCESS; + + default: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; + } +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T simple_writer_open( VC_CONTAINER_T *ctx ) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; + const char *extension = vc_uri_path_extension(ctx->priv->uri); + VC_CONTAINER_MODULE_T *module; + + /* Check if the user has specified a container */ + vc_uri_find_query(ctx->priv->uri, 0, "container", &extension); + + /* Check we're the right writer for this */ + if(!extension) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if(strcasecmp(extension, "smpl") && strcasecmp(extension, "simple")) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + LOG_DEBUG(ctx, "using simple writer"); + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if (!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + ctx->priv->module = module; + ctx->tracks = module->tracks; + + ctx->priv->pf_close = simple_writer_close; + ctx->priv->pf_write = simple_writer_write; + ctx->priv->pf_control = simple_writer_control; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(ctx, "simple: error opening stream (%i)", status); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak writer_open simple_writer_open +#endif diff --git a/containers/test/check_frame_int.c b/containers/test/check_frame_int.c new file mode 100644 index 0000000..1819c47 --- /dev/null +++ b/containers/test/check_frame_int.c @@ -0,0 +1,348 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_io.h" + +#define BUFFER_SIZE 256*1024 +#define MAX_TRACKS 16 +#define MAX_SEEKS 16 + +static int container_test_info(VC_CONTAINER_T *ctx, bool b_reader); +static int container_test_parse_cmdline(int argc, char **argv); + +static const char *psz_in = 0; +static long packets_num = 0; +static long track_num = -1; +static int fps = 30; +static int margin = 5; // margin for frame interval, percent + +static struct +{ + uint32_t mapping; + uint32_t frames; + uint32_t packets; + uint64_t bytes; + uint32_t frame_size; + int64_t first_dts; + int64_t first_pts; + int64_t last_dts; + int64_t last_pts; +} tracks[MAX_TRACKS]; + +static int32_t verbosity = VC_CONTAINER_LOG_ERROR|VC_CONTAINER_LOG_INFO; + +/*****************************************************************************/ +int main(int argc, char **argv) +{ + int retval = 0; + VC_CONTAINER_T *p_ctx = 0; + VC_CONTAINER_STATUS_T status; + unsigned int i; + uint8_t *buffer = malloc(BUFFER_SIZE); + int32_t interval; + int64_t last_packet_pts = -1; + int32_t max_interval = 0, max_interval_after_first = 0; + int fail = 0; + + if(container_test_parse_cmdline(argc, argv)) + goto error_silent; + + LOG_INFO (0, "Require that no frame interval greater than 1/%d seconds (%d%% margin)", fps, margin); + + /* Set the general verbosity */ + vc_container_log_set_verbosity(0, verbosity); + vc_container_log_set_default_verbosity(verbosity); + + p_ctx = vc_container_open_reader(psz_in, &status, 0, 0); + + if(!p_ctx) + { + LOG_ERROR(0, "error opening file %s (%i)", psz_in, status); + goto error; + } + + if (verbosity & VC_CONTAINER_LOG_DEBUG) + { + container_test_info(p_ctx, true); + } + LOG_INFO (0, "Search for video track only"); + + /* Disabling tracks which are not requested and enable packetisation if requested */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[i]; + track->is_enabled = (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO); + } + + + LOG_DEBUG(0, "TEST start reading"); + for(i = 0; !packets_num || (long)i < packets_num; i++) + { + VC_CONTAINER_PACKET_T packet = {0}; + int32_t frame_num = 0; + + status = vc_container_read(p_ctx, &packet, VC_CONTAINER_READ_FLAG_INFO); + if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST info status: %i", status); break;} + + if(packet.track < MAX_TRACKS) + { + if((packet.flags & VC_CONTAINER_PACKET_FLAG_FRAME_START)) + { + tracks[packet.track].frames++; + tracks[packet.track].frame_size = 0; + } + frame_num = tracks[packet.track].frames; + } + + tracks[packet.track].frame_size += packet.size; + +// LOG_DEBUG(0, "packet info: track %i, size %i/%i/%i, pts %"PRId64", flags %x%s, num %i", +// packet.track, packet.size, packet.frame_size, tracks[packet.track].frame_size, packet.pts, packet.flags, +// (packet.flags & VC_CONTAINER_PACKET_FLAG_KEYFRAME) ? " (keyframe)" : "", +// frame_num-1); + + if (last_packet_pts != -1) + interval = packet.pts - last_packet_pts; + else + interval = 0; // packet.pts; + last_packet_pts = packet.pts; + max_interval = MAX (max_interval, interval); + if (i >= 2) + max_interval_after_first = MAX (max_interval_after_first, interval); + + /* Check if interval (in us) exceeds 1/fps, with percentage margin */ + if (interval * fps > 1e4 * (100+margin)) + { + LOG_INFO (0, "Frame %d, interval %.3f FAILED", i, interval / 1000.0f); + fail = 1; + } + + LOG_DEBUG (0, "Frame %d, interval %.3f", i, interval / 1000.0f); + + if(track_num >= 0 && packet.track != (uint32_t)track_num) + { + status = vc_container_read(p_ctx, 0, VC_CONTAINER_READ_FLAG_SKIP); + if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST skip status: %i", status); break;} + continue; + } + + packet.buffer_size = BUFFER_SIZE; + packet.data = buffer; + packet.size = 0; + status = vc_container_read(p_ctx, &packet, 0); + if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST read status: %i", status); break;} + + // LOG_DEBUG(0, "packet: track %i, size %i, pts %"PRId64", flags %x", packet.track, packet.size, packet.pts, packet.flags); + + if (tracks[packet.track].packets) + { + tracks[packet.track].last_dts = packet.dts; + tracks[packet.track].last_pts = packet.pts; + } else { + tracks[packet.track].first_dts = packet.dts; + tracks[packet.track].first_pts = packet.pts; + } + + if(packet.track < MAX_TRACKS) + { + tracks[packet.track].packets++; + tracks[packet.track].bytes += packet.size; + } + + } + LOG_DEBUG(0, "TEST stop reading"); + + /* Output stats */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + LOG_INFO(0, "track %u: read %u samples in %u packets for a total of %"PRIu64" bytes", + i, tracks[i].frames, tracks[i].packets, tracks[i].bytes); + LOG_INFO(0, "Starting at %"PRId64"us (decode at %"PRId64"us), ending at %"PRId64"us (decode at %"PRId64"us)", + tracks[i].first_pts, tracks[i].first_dts, tracks[i].last_pts, tracks[i].last_dts); + } + + LOG_INFO (0, "---\nMax interval = %.3f ms; max interval (after first) = %.3f ms\n", (float) max_interval / 1000.0, (float) max_interval_after_first / 1000.0); + + end: + if(p_ctx) vc_container_close(p_ctx); + free(buffer); + +#ifdef _MSC_VER + getchar(); +#endif + + retval = fail; + if (fail) + { + LOG_INFO (0, "TEST FAILED: highest frame interval = %.3f ms", max_interval / 1000.0f); + } + else + LOG_INFO (0, "TEST PASSED"); + return retval; + + error: + LOG_ERROR(0, "TEST FAILED TO RUN"); + error_silent: + retval = -1; + goto end; +} + +static int container_test_parse_cmdline(int argc, char **argv) +{ + int i, j, k; + int32_t *p_verbosity; + + /* Parse the command line arguments */ + for(i = 1; i < argc; i++) + { + if(!argv[i]) continue; + + if(argv[i][0] != '-') + { + /* Not an option argument so will be the input URI */ + psz_in = argv[i]; + continue; + } + + /* We are now dealing with command line options */ + switch(argv[i][1]) + { + case 'v': + j = 2; + p_verbosity = &verbosity; + *p_verbosity = VC_CONTAINER_LOG_ERROR|VC_CONTAINER_LOG_INFO; + for(k = 0; k < 2 && argv[i][j+k] == 'v'; k++) + *p_verbosity = (*p_verbosity << 1) | 1 ; + break; + case 'f': + if(i+1 == argc || !argv[i+1]) goto invalid_option; + fps = strtol(argv[++i], 0, 0); + break; + case 'h': goto usage; + default: goto invalid_option; + } + continue; + } + + /* Sanity check that we have at least an input uri */ + if(!psz_in) + { + LOG_ERROR(0, "missing uri argument"); + goto usage; + } + + return 0; + + invalid_option: + LOG_ERROR(0, "invalid command line option (%s)", argv[i]); + + usage: + psz_in = strrchr(argv[0], '\\'); if(psz_in) psz_in++; + if(!psz_in) {psz_in = strrchr(argv[0], '/'); if(psz_in) psz_in++;} + if(!psz_in) psz_in = argv[0]; + LOG_INFO(0, ""); + LOG_INFO(0, "usage: %s [options] uri", psz_in); + LOG_INFO(0, "options list:"); + LOG_INFO(0, " -vxx : general verbosity level (replace xx with a number of \'v\')"); + LOG_INFO(0, " -f : required frame rate/second (frame interval must not exceed 1/f)"); + LOG_INFO(0, " -h : help"); + return 1; +} + +static int container_test_info(VC_CONTAINER_T *ctx, bool b_reader) +{ + const char *name_type; + unsigned int i; + + LOG_INFO(0, ""); + if(b_reader) LOG_INFO(0, "----Reader Information----"); + else LOG_INFO(0, "----Writer Information----"); + + LOG_INFO(0, "duration: %2.2fs, size: %"PRId64, ctx->duration/1000000.0, ctx->size); + LOG_INFO(0, "capabilities: %x", ctx->capabilities); + LOG_INFO(0, ""); + + for(i = 0; i < ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[i]; + + switch(track->format->es_type) + { + case VC_CONTAINER_ES_TYPE_AUDIO: name_type = "audio"; break; + case VC_CONTAINER_ES_TYPE_VIDEO: name_type = "video"; break; + case VC_CONTAINER_ES_TYPE_SUBPICTURE: name_type = "subpicture"; break; + default: name_type = "unknown"; break; + } + + if (!strcmp (name_type, "video")) + { + LOG_INFO(0, "track: %i, type: %s, fourcc: %4.4s", i, name_type, (char *)&track->format->codec); + LOG_INFO(0, " bitrate: %i, framed: %i, enabled: %i", track->format->bitrate, + !!(track->format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED), track->is_enabled); + LOG_INFO(0, " extra data: %i, %p", track->format->extradata_size, track->format->extradata); + switch(track->format->es_type) + { + case VC_CONTAINER_ES_TYPE_VIDEO: + LOG_INFO(0, " width: %i, height: %i, (%i,%i,%i,%i)", + track->format->type->video.width, track->format->type->video.height, + track->format->type->video.x_offset, track->format->type->video.y_offset, + track->format->type->video.visible_width, track->format->type->video.visible_height); + LOG_INFO(0, " pixel aspect ratio: %i/%i, frame rate: %i/%i", + track->format->type->video.par_num, track->format->type->video.par_den, + track->format->type->video.frame_rate_num, track->format->type->video.frame_rate_den); + break; + + default: break; + } + } + } + + for (i = 0; i < ctx->meta_num; ++i) + { + const char *name, *value; + if (i == 0) LOG_INFO(0, ""); + name = vc_container_metadata_id_to_string(ctx->meta[i]->key); + value = ctx->meta[i]->value; + if(!name) continue; + LOG_INFO(0, "metadata(%i) : %s : %s", i, name, value); + } + + LOG_INFO(0, "--------------------------"); + LOG_INFO(0, ""); + + return 0; +} + + diff --git a/containers/test/datagram_receiver.c b/containers/test/datagram_receiver.c new file mode 100644 index 0000000..30d661f --- /dev/null +++ b/containers/test/datagram_receiver.c @@ -0,0 +1,80 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "containers/net/net_sockets.h" + +int main(int argc, char **argv) +{ + VC_CONTAINER_NET_T *sock; + vc_container_net_status_t status; + char *buffer; + size_t buffer_size; + size_t received; + + if (argc < 2) + { + printf("Usage:\n%s \n", argv[0]); + return 1; + } + + sock = vc_container_net_open(NULL, argv[1], 0, &status); + if (!sock) + { + printf("vc_container_net_open failed: %d\n", status); + return 2; + } + + buffer_size = vc_container_net_maximum_datagram_size(sock); + buffer = (char *)malloc(buffer_size); + if (!buffer) + { + vc_container_net_close(sock); + printf("Failure allocating buffer\n"); + return 3; + } + + while ((received = vc_container_net_read(sock, buffer, buffer_size)) != 0) + { + char *ptr = buffer; + + while (received--) + putchar(*ptr++); + } + + if (vc_container_net_status(sock) != VC_CONTAINER_NET_SUCCESS) + { + printf("vc_container_net_read failed: %d\n", vc_container_net_status(sock)); + } + + vc_container_net_close(sock); + + return 0; +} diff --git a/containers/test/datagram_sender.c b/containers/test/datagram_sender.c new file mode 100644 index 0000000..6ed7b15 --- /dev/null +++ b/containers/test/datagram_sender.c @@ -0,0 +1,77 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "containers/net/net_sockets.h" + +int main(int argc, char **argv) +{ + VC_CONTAINER_NET_T *sock; + vc_container_net_status_t status; + char *buffer; + size_t buffer_size; + + if (argc < 3) + { + printf("Usage:\n%s
\n", argv[0]); + return 1; + } + + sock = vc_container_net_open(argv[1], argv[2], 0, &status); + if (!sock) + { + printf("vc_container_net_open failed: %d\n", status); + return 2; + } + + buffer_size = vc_container_net_maximum_datagram_size(sock); + buffer = (char *)malloc(buffer_size); + if (!buffer) + { + vc_container_net_close(sock); + printf("Failure allocating buffer\n"); + return 3; + } + + printf("Don't enter more than %d characters in one line!\n", (int)buffer_size); + + while (fgets(buffer, buffer_size, stdin)) + { + if (!vc_container_net_write(sock, buffer, strlen(buffer))) + { + printf("vc_container_net_write failed: %d\n", vc_container_net_status(sock)); + break; + } + } + + vc_container_net_close(sock); + + return 0; +} diff --git a/containers/test/dump_pktfile.c b/containers/test/dump_pktfile.c new file mode 100644 index 0000000..eddf197 --- /dev/null +++ b/containers/test/dump_pktfile.c @@ -0,0 +1,147 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#define PACKET_BUFFER_SIZE 32768 + +enum { + SUCCESS = 0, + SHOWED_USAGE, + FAILED_TO_OPEN_PKTFILE, + FAILED_TO_OPEN_OUTPUT_FILE, + BYTE_ORDER_MARK_MISSING, + INVALID_BYTE_ORDER_MARK, + MEMORY_ALLOCATION_FAILURE, + PACKET_TOO_BIG, +}; + +/** Native byte order word */ +#define NATIVE_BYTE_ORDER 0x50415753U +/** Reverse of native byte order - need to swap bytes around */ +#define SWAP_BYTE_ORDER 0x53574150U + +static unsigned long reverse_byte_order( unsigned long value ) +{ + /* Reverse the order of the bytes in the word */ + return ((value << 24) | ((value & 0xFF00) << 8) | ((value >> 8) & 0xFF00) | (value >> 24)); +} + +int main(int argc, char **argv) +{ + int status = SUCCESS; + FILE *pktfile = NULL, *output_file = NULL; + uint32_t byte_order, packet_size, packet_read; + char *buffer = NULL; + + if (argc < 2) + { + printf("\ +Usage:\n\ + %s []\n\ + is the file to read.\n\ +, if given, will receive the unpacketized data.\n", argv[0]); + status = SHOWED_USAGE; + goto end_program; + } + + pktfile = fopen(argv[1], "rb"); + if (!pktfile) + { + printf("Failed to open pkt file <%s> for reading.\n", argv[1]); + status = FAILED_TO_OPEN_PKTFILE; + goto end_program; + } + + if (fread(&byte_order, 1, sizeof(byte_order), pktfile) != sizeof(byte_order)) + { + printf("Failed to read byte order header from pkt file.\n"); + status = BYTE_ORDER_MARK_MISSING; + goto end_program; + } + + if (byte_order != NATIVE_BYTE_ORDER && byte_order != SWAP_BYTE_ORDER) + { + printf("Invalid byte order header: 0x%08x (%u)\n", byte_order, byte_order); + status = INVALID_BYTE_ORDER_MARK; + goto end_program; + } + + buffer = (char *)malloc(PACKET_BUFFER_SIZE); + if (!buffer) + { + printf("Memory allocation failed\n"); + status = MEMORY_ALLOCATION_FAILURE; + goto end_program; + } + + if (argc > 2) + { + output_file = fopen(argv[2], "wb"); + if (!output_file) + { + printf("Failed to open <%s> for output.\n", argv[2]); + status = FAILED_TO_OPEN_OUTPUT_FILE; + goto end_program; + } + } + + while (fread(&packet_size, 1, sizeof(packet_size), pktfile) == sizeof(packet_size)) + { + if (byte_order == SWAP_BYTE_ORDER) + packet_size = reverse_byte_order(packet_size); + + if (packet_size >= PACKET_BUFFER_SIZE) + { + printf("*** Packet size is bigger than buffer (%u > %u)\n", packet_size, PACKET_BUFFER_SIZE - 1); + status = PACKET_TOO_BIG; + goto end_program; + } + + packet_read = fread(buffer, 1, packet_size, pktfile); + + if (output_file) + { + fwrite(buffer, 1, packet_size, output_file); + } else { + buffer[packet_size] = '\0'; + printf("%s", buffer); + getchar(); + } + + if (packet_read != packet_size) + printf("*** Invalid packet size (expected %u, got %u)\n", packet_size, packet_read); + } + +end_program: + if (pktfile) fclose(pktfile); + if (output_file) fclose(output_file); + if (buffer) free(buffer); + return -status; +} diff --git a/containers/test/nb_io.h b/containers/test/nb_io.h new file mode 100644 index 0000000..2058fe6 --- /dev/null +++ b/containers/test/nb_io.h @@ -0,0 +1,47 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _NB_IO_H_ +#define _NB_IO_H_ + +/** Set whether console input is non-blocking or not. + * + * \param enable Pass true to set input as non-blocking, false to set it as blocking again.*/ +void nb_set_nonblocking_input(int enable); + +/** \return Non-zero when there is a character available to read using nb_get_char(). */ +int nb_char_available(void); + +/** \return The next character available from the console, or zero. */ +char nb_get_char(void); + +/** Put the character out to the console. + * + * \param ch The character to be output. */ +void nb_put_char(char ch); + +#endif /* _NB_IO_H_ */ diff --git a/containers/test/nb_io_unix.c b/containers/test/nb_io_unix.c new file mode 100644 index 0000000..63e1a1f --- /dev/null +++ b/containers/test/nb_io_unix.c @@ -0,0 +1,80 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include "nb_io.h" + +void nb_set_nonblocking_input(int enable) +{ + struct termios ttystate; + + //get the terminal state + tcgetattr(STDIN_FILENO, &ttystate); + + if (enable) + { + //turn off canonical mode + ttystate.c_lflag &= ~ICANON; + //minimum of number input read. + ttystate.c_cc[VMIN] = 1; + } + else + { + //turn on canonical mode + ttystate.c_lflag |= ICANON; + } + + //set the terminal attributes. + tcsetattr(STDIN_FILENO, TCSANOW, &ttystate); +} + +int nb_char_available(void) +{ + struct timeval tv; + fd_set fds; + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + select(STDIN_FILENO+1, &fds, NULL, NULL, &tv); + return (FD_ISSET(0, &fds)); +} + +char nb_get_char(void) +{ + return getchar(); +} + +void nb_put_char(char ch) +{ + putchar(ch); +} diff --git a/containers/test/nb_io_win32.c b/containers/test/nb_io_win32.c new file mode 100644 index 0000000..a402bcb --- /dev/null +++ b/containers/test/nb_io_win32.c @@ -0,0 +1,53 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include /* For _kbhit() */ + +#include "nb_io.h" + +void nb_set_nonblocking_input(int enable) +{ + /* No need to do anything in Win32 */ + (void)enable; +} + +int nb_char_available() +{ + return _kbhit(); +} + +char nb_get_char() +{ + char c = _getch(); + _putch(c); /* Echo back */ + return c; +} + +void nb_put_char(char ch) +{ + _putch(ch); +} diff --git a/containers/test/rtp_decoder.c b/containers/test/rtp_decoder.c new file mode 100644 index 0000000..c089576 --- /dev/null +++ b/containers/test/rtp_decoder.c @@ -0,0 +1,449 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "containers/containers.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_io.h" + +#include "nb_io.h" + +#define MAXIMUM_BUFFER_SIZE 65000 +#define MINIMUM_BUFFER_SPACE 1500 + +#define INITIAL_READ_BUFFER_SIZE 8000 +#define MAXIMUM_READ_BUFFER_SIZE 64000 + +#define BYTES_PER_ROW 32 + +#define HAS_PADDING 0x20 +#define HAS_EXTENSION 0x10 +#define CSRC_COUNT_MASK 0x0F + +#define HAS_MARKER 0x80 +#define PAYLOAD_TYPE_MASK 0x7F + +#define EXTENSION_LENGTH_MASK 0x0000FFFF +#define EXTENSION_ID_SHIFT 16 + +#define LOWEST_VERBOSITY 1 +#define BASIC_HEADER_VERBOSITY 2 +#define FULL_HEADER_VERBOSITY 3 +#define FULL_PACKET_VERBOSITY 4 + +#define ESCAPE_CHARACTER 0x1B + +static bool seen_first_packet; +static uint16_t expected_next_seq_num; + +static bool do_print_usage; +static uint32_t verbosity; +static const char *read_uri; +static const char *packet_save_file; +static bool packet_save_is_pktfile; + +static uint16_t network_to_host_16(const uint8_t *buffer) +{ + return (buffer[0] << 8) | buffer[1]; +} + +static uint32_t network_to_host_32(const uint8_t *buffer) +{ + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; +} + +/** Avoid alignment problems when writing a word value to the buffer */ +static void store_u32(uint8_t *buffer, uint32_t value) +{ + buffer[0] = (uint8_t)value; + buffer[1] = (uint8_t)(value >> 8); + buffer[2] = (uint8_t)(value >> 16); + buffer[3] = (uint8_t)(value >> 24); +} + +/** Avoid alignment problems when reading a word value from the buffer */ +static uint32_t fetch_u32(uint8_t *buffer) +{ + return (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; +} + +static bool marker_bit_set(const uint8_t *buffer, size_t buffer_len) +{ + if (buffer_len < 2) + return false; + + return (buffer[1] & HAS_MARKER); +} + +static void dump_bytes(const uint8_t *buffer, size_t buffer_len) +{ + char dump_str[3 * BYTES_PER_ROW + 1]; + int in_row = 0; + + while (buffer_len--) + { + sprintf(dump_str + 3 * in_row, "%2.2X ", *buffer++); + if (++in_row == BYTES_PER_ROW) + { + LOG_INFO(NULL, dump_str); + in_row = 0; + } + } + + if (in_row) + { + LOG_INFO(NULL, dump_str); + } +} + +static bool decode_packet(const uint8_t *buffer, size_t buffer_len) +{ + uint8_t flags; + uint8_t payload_type; + uint16_t seq_num; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc_count; + + if (buffer_len < 12) + { + LOG_ERROR(NULL, "Packet too small: basic header missing"); + return false; + } + + flags = buffer[0]; + payload_type = buffer[1]; + seq_num = network_to_host_16(buffer + 2); + timestamp = network_to_host_32(buffer + 4); + ssrc = network_to_host_32(buffer + 8); + + if (seen_first_packet && seq_num != expected_next_seq_num) + { + int16_t missing_packets = seq_num - expected_next_seq_num; + + LOG_INFO(NULL, "*** Sequence break, expected %hu, got %hu ***", expected_next_seq_num, seq_num); + if (missing_packets > 0) + LOG_INFO(NULL, "*** Jumped forward %hd packets ***", missing_packets); + else + LOG_INFO(NULL, "*** Jumped backward %hd packets ***", -missing_packets); + } + seen_first_packet = true; + expected_next_seq_num = seq_num + 1; + + /* Dump the basic header information */ + if (verbosity >= BASIC_HEADER_VERBOSITY) + { + LOG_INFO(NULL, "Version: %d\nPayload type: %d%s\nSequence: %d\nTimestamp: %u\nSSRC: 0x%8.8X", + flags >> 6, payload_type & PAYLOAD_TYPE_MASK, + (const char *)((payload_type & HAS_MARKER) ? " (M)" : ""), + seq_num, timestamp, ssrc); + } + + buffer += 12; + buffer_len -= 12; + + if (verbosity >= FULL_HEADER_VERBOSITY) + { + /* Dump the CSRCs, if any */ + csrc_count = flags & CSRC_COUNT_MASK; + if (csrc_count) + { + uint32_t ii; + + if (buffer_len < (csrc_count * 4)) + { + LOG_ERROR(NULL, "Packet too small: CSRCs missing"); + return false; + } + + LOG_INFO(NULL, "CSRCs:"); + for (ii = 0; ii < csrc_count; ii++) + { + LOG_INFO(NULL, " 0x%8.8X", network_to_host_32(buffer)); + buffer += 4; + buffer_len -= 4; + } + } + + /* Dump any extension, if present */ + if (flags & HAS_EXTENSION) + { + uint32_t extension_hdr; + uint32_t extension_id; + size_t extension_len; + + if (buffer_len < 4) + { + LOG_ERROR(NULL, "Packet too small: extension header missing"); + return false; + } + + extension_hdr = network_to_host_32(buffer); + buffer += 4; + buffer_len -= 4; + + extension_len = (size_t)(extension_hdr & EXTENSION_LENGTH_MASK); + extension_id = extension_hdr >> EXTENSION_ID_SHIFT; + + if (buffer_len < extension_len) + { + LOG_ERROR(NULL, "Packet too small: extension content missing"); + return false; + } + + LOG_INFO(NULL, "Extension: 0x%4.4X (%u bytes)", extension_id, (unsigned)extension_len); + dump_bytes(buffer, extension_len); + buffer += extension_len; + buffer_len -= extension_len; + } + } + + /* And finally the payload data */ + if (verbosity >= FULL_PACKET_VERBOSITY) + { + LOG_INFO(NULL, "Data: (%u bytes)", (unsigned)buffer_len); + dump_bytes(buffer, buffer_len); + } + + return true; +} + +static void increase_read_buffer_size(VC_CONTAINER_IO_T *p_ctx) +{ + uint32_t buffer_size = INITIAL_READ_BUFFER_SIZE; + + /* Iteratively enlarge read buffer until either operation fails or maximum is reached. */ + while (vc_container_io_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE, buffer_size) == VC_CONTAINER_SUCCESS) + { + buffer_size <<= 1; /* Double and try again */ + if (buffer_size > MAXIMUM_READ_BUFFER_SIZE) + break; + } +} + +static void parse_command_line(int argc, char **argv) +{ + int arg = 1; + + while (arg < argc) + { + if (*argv[arg] != '-') /* End of options, next should be URI */ + break; + + switch (argv[arg][1]) + { + case 'h': + do_print_usage = true; + break; + case 's': + arg++; + if (arg >= argc) + break; + packet_save_file = argv[arg]; + packet_save_is_pktfile = (strncmp(packet_save_file, "pktfile:", 8) == 0); + break; + case 'v': + { + const char *ptr = &argv[arg][2]; + + verbosity = 1; + while (*ptr++ == 'v') + verbosity++; + } + break; + default: LOG_ERROR(NULL, "Unrecognised option: %s", argv[arg]); return; + } + + arg++; + } + + if (arg < argc) + read_uri = argv[arg]; +} + +static void print_usage(char *program_name) +{ + LOG_INFO(NULL, "Usage:"); + LOG_INFO(NULL, " %s [opts] ", program_name); + LOG_INFO(NULL, "Reads RTP packets from , decodes to standard output."); + LOG_INFO(NULL, "Press the escape key to terminate the program."); + LOG_INFO(NULL, "Options:"); + LOG_INFO(NULL, " -h Print this information"); + LOG_INFO(NULL, " -s x Save packets to URI x"); + LOG_INFO(NULL, " -v Dump standard packet header"); + LOG_INFO(NULL, " -vv Dump entire header"); + LOG_INFO(NULL, " -vvv Dump entire header and data"); +} + +int main(int argc, char **argv) +{ + int result = 0; + uint8_t *buffer = NULL; + VC_CONTAINER_IO_T *read_io = NULL; + VC_CONTAINER_IO_T *write_io = NULL; + VC_CONTAINER_STATUS_T status; + size_t received_bytes; + bool ready = true; + uint32_t available_bytes; + uint8_t *packet_ptr; + + parse_command_line(argc, argv); + + if (do_print_usage || !read_uri) + { + print_usage(argv[0]); + result = 1; goto tidyup; + } + + buffer = (uint8_t *)malloc(MAXIMUM_BUFFER_SIZE); + if (!buffer) + { + LOG_ERROR(NULL, "Allocating %d bytes for the buffer failed", MAXIMUM_BUFFER_SIZE); + result = 2; goto tidyup; + } + + read_io = vc_container_io_open(read_uri, VC_CONTAINER_IO_MODE_READ, &status); + if (!read_io) + { + LOG_ERROR(NULL, "Opening <%s> for read failed: %d", read_uri, status); + result = 3; goto tidyup; + } + + increase_read_buffer_size(read_io); + + if (packet_save_file) + { + write_io = vc_container_io_open(packet_save_file, VC_CONTAINER_IO_MODE_WRITE, &status); + if (!write_io) + { + LOG_ERROR(NULL, "Opening <%s> for write failed: %d", packet_save_file, status); + result = 4; goto tidyup; + } + if (!packet_save_is_pktfile) + { + store_u32(buffer, 0x50415753); + vc_container_io_write(write_io, buffer, sizeof(uint32_t)); + } + } + + /* Use non-blocking I/O for both network and console */ + vc_container_io_control(read_io, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 20); + nb_set_nonblocking_input(1); + + packet_ptr = buffer; + available_bytes = MAXIMUM_BUFFER_SIZE - sizeof(uint32_t); + while (ready) + { + /* Read a packet and store its length in the word before it */ + received_bytes = vc_container_io_read(read_io, packet_ptr + sizeof(uint32_t), available_bytes); + if (received_bytes) + { + bool packet_has_marker; + + store_u32(packet_ptr, received_bytes); + packet_ptr += sizeof(uint32_t); + packet_has_marker = marker_bit_set(packet_ptr, received_bytes); + packet_ptr += received_bytes; + available_bytes -= received_bytes + sizeof(uint32_t); + + if (packet_has_marker || (available_bytes < MINIMUM_BUFFER_SPACE)) + { + uint8_t *decode_ptr; + + if (write_io && !packet_save_is_pktfile) + { + uint32_t total_bytes = packet_ptr - buffer; + if (vc_container_io_write(write_io, buffer, total_bytes) != total_bytes) + { + LOG_ERROR(NULL, "Error saving packets to file"); + break; + } + if (verbosity >= LOWEST_VERBOSITY) + LOG_INFO(NULL, "Written %u bytes to file", total_bytes); + } + + for (decode_ptr = buffer; decode_ptr < packet_ptr;) + { + received_bytes = fetch_u32(decode_ptr); + decode_ptr += sizeof(uint32_t); + + if (write_io && packet_save_is_pktfile) + { + if (vc_container_io_write(write_io, buffer, received_bytes) != received_bytes) + { + LOG_ERROR(NULL, "Error saving packets to file"); + break; + } + if (verbosity >= LOWEST_VERBOSITY) + LOG_INFO(NULL, "Written %u bytes to file", received_bytes); + } + + if (!decode_packet(decode_ptr, received_bytes)) + { + LOG_ERROR(NULL, "Failed to decode packet"); + break; + } + decode_ptr += received_bytes; + } + + /* Reset to start of buffer */ + packet_ptr = buffer; + available_bytes = MAXIMUM_BUFFER_SIZE - sizeof(uint32_t); + } + } + + if (nb_char_available()) + { + if (nb_get_char() == ESCAPE_CHARACTER) + ready = false; + } + + switch (read_io->status) + { + case VC_CONTAINER_SUCCESS: + case VC_CONTAINER_ERROR_CONTINUE: + break; + default: + ready = false; + } + } + + nb_set_nonblocking_input(0); + +tidyup: + if (write_io) + vc_container_io_close(write_io); + if (read_io) + vc_container_io_close(read_io); + if (buffer) + free(buffer); + + return result; +} diff --git a/containers/test/stream_client.c b/containers/test/stream_client.c new file mode 100644 index 0000000..b1c015e --- /dev/null +++ b/containers/test/stream_client.c @@ -0,0 +1,145 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +#include "containers/net/net_sockets.h" + +#include "nb_io.h" + +#define MAX_BUFFER_LEN 1024 +/* Time in milliseconds to yield when nothing is happening */ +#define YIELD_PERIOD_MS 33 + + +static vc_container_net_status_t local_net_control(VC_CONTAINER_NET_T *sock, + vc_container_net_control_t operation, ...) +{ + vc_container_net_status_t result; + va_list args; + + va_start(args, operation); + result = vc_container_net_control(sock, operation, args); + va_end(args); + + return result; +} + +int main(int argc, char **argv) +{ + VC_CONTAINER_NET_T *sock; + vc_container_net_status_t status; + char send_buffer[MAX_BUFFER_LEN]; + char recv_buffer[MAX_BUFFER_LEN]; + int ready = 1; + int to_send = 0; + size_t received; + + if (argc < 3) + { + printf("Usage:\n%s
\n", argv[0]); + return 1; + } + + sock = vc_container_net_open(argv[1], argv[2], VC_CONTAINER_NET_OPEN_FLAG_STREAM, &status); + if (!sock) + { + printf("vc_container_net_open failed: %d\n", status); + return 2; + } + + /* Use non-blocking I/O for both network and console */ + local_net_control(sock, VC_CONTAINER_NET_CONTROL_SET_READ_TIMEOUT_MS, YIELD_PERIOD_MS); + nb_set_nonblocking_input(1); + + while (ready) + { + if (nb_char_available()) + { + char c = nb_get_char(); + + if (c == 26) /* CTRL+Z */ + break; + + send_buffer[to_send++] = c; + + if (c == '\r') /* Translate CR into CRLF */ + { + c = '\n'; + nb_put_char(c); + send_buffer[to_send++] = c; + } + + if (c == '\n' || to_send == sizeof(send_buffer) - 1) /* Allow for next character needing translation */ + { + int already_sent = 0, sent; + + /* Send a line at a time */ + while (to_send) + { + sent = vc_container_net_write(sock, send_buffer + already_sent, to_send); + if (!sent) + { + printf("vc_container_net_write failed: %d\n", vc_container_net_status(sock)); + to_send = 0; + ready = 0; + break; + } + to_send -= sent; + already_sent += sent; + } + } + } + + /* Read back and print any data from the server */ + received = vc_container_net_read(sock, recv_buffer, sizeof(recv_buffer) - 1); + if (received) + { + char *ptr = recv_buffer; + + while (received--) + nb_put_char(*ptr++); + } else { + vc_container_net_status_t net_status = vc_container_net_status(sock); + + if (net_status != VC_CONTAINER_NET_ERROR_TIMED_OUT && net_status != VC_CONTAINER_NET_SUCCESS) + { + printf("vc_container_net_read failed: %d\n", net_status); + ready = 0; + } + } + } + + nb_set_nonblocking_input(0); + + vc_container_net_close(sock); + + return 0; +} diff --git a/containers/test/stream_server.c b/containers/test/stream_server.c new file mode 100644 index 0000000..8dead8f --- /dev/null +++ b/containers/test/stream_server.c @@ -0,0 +1,148 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include "containers/net/net_sockets.h" + +#define MAX_BUFFER_LEN 1024 +#define MAX_NAME_LEN 256 +#define MAX_PORT_LEN 32 + +int main(int argc, char **argv) +{ + VC_CONTAINER_NET_T *server_sock, *sock; + vc_container_net_status_t status; + char buffer[MAX_BUFFER_LEN]; + char name[MAX_NAME_LEN]; + unsigned short port; + int ii, connections = 1; + size_t received; + + if (argc < 2) + { + printf("Usage:\n%s []\n", argv[0]); + return 1; + } + + server_sock = vc_container_net_open(NULL, argv[1], VC_CONTAINER_NET_OPEN_FLAG_STREAM, &status); + if (!server_sock) + { + printf("vc_container_net_open failed: %d\n", status); + return 2; + } + + if (argc > 2) + { + sscanf(argv[2], "%d", &connections); + } + + status = vc_container_net_listen(server_sock, connections); + if (status != VC_CONTAINER_NET_SUCCESS) + { + printf("vc_container_net_listen failed: %d\n", status); + vc_container_net_close(server_sock); + return 3; + } + + for (ii = 0; ii < connections; ii++) + { + status = vc_container_net_accept(server_sock, &sock); + if (status != VC_CONTAINER_NET_SUCCESS) + { + printf("vc_container_net_accept failed: %d\n", status); + vc_container_net_close(server_sock); + return 3; + } + + strcpy(name, ""); + vc_container_net_get_client_name(sock, name, sizeof(name)); + vc_container_net_get_client_port(sock, &port); + printf("Connection from %s:%hu\n", name, port); + + while ((received = vc_container_net_read(sock, buffer, sizeof(buffer) - 1)) != 0) + { + char *ptr = buffer; + size_t jj; + + printf("Rx:"); + + /* Flip case and echo data back to client */ + for (jj = 0; jj < received; jj++, ptr++) + { + char c = *ptr; + + putchar(c); + if (isalpha((unsigned char)c)) + *ptr ^= 0x20; /* Swaps case of ASCII alphabetic characters */ + } + + ptr = buffer; + + printf("Tx:"); + while (received) + { + size_t sent; + + sent = vc_container_net_write(sock, ptr, received); + if (!sent) + { + status = vc_container_net_status(sock); + printf("vc_container_net_write failed: %d\n", status); + break; + } + + /* Print out bytes actually sent */ + while (sent--) + { + received--; + putchar(*ptr++); + } + } + } + + status = vc_container_net_status(sock); + + if (status != VC_CONTAINER_NET_SUCCESS && status != VC_CONTAINER_NET_ERROR_CONNECTION_LOST) + break; + + vc_container_net_close(sock); + } + + if (status != VC_CONTAINER_NET_SUCCESS) + { + printf("vc_container_net_read failed: %d\n", status); + } + + vc_container_net_close(server_sock); + + return 0; +} diff --git a/containers/test/test.c b/containers/test/test.c new file mode 100644 index 0000000..73cbecd --- /dev/null +++ b/containers/test/test.c @@ -0,0 +1,623 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_io.h" + +#define BUFFER_SIZE 256*1024 +#define MAX_TRACKS 16 +#define MAX_SEEKS 16 + +static int container_test_info(VC_CONTAINER_T *ctx, bool b_reader); +static int container_test_parse_cmdline(int argc, char **argv); + +/* Client i/o wrapper */ +static VC_CONTAINER_IO_T *client_io_open(const char *, VC_CONTAINER_STATUS_T *); + +static bool b_info = 0, b_seek = 0, b_dump = 0; +static bool b_audio = 1, b_video = 1, b_subs = 1, b_errorcode = 1; +static const char *psz_in = 0, *psz_out = 0; +static long packets_num = 0; +static long track_num = -1; +static FILE *dump_file = 0; +static bool b_client_io = 0; +static bool b_packetize = 0; + +static struct +{ + uint32_t mapping; + uint32_t frames; + uint32_t packets; + uint64_t bytes; + uint32_t frame_size; + int64_t first_dts; + int64_t first_pts; + int64_t last_dts; + int64_t last_pts; +} tracks[MAX_TRACKS]; + +static unsigned int seeks = 0; +static long seek_offsets[MAX_SEEKS]; +static long seek_flags[MAX_SEEKS]; +static int32_t verbosity = VC_CONTAINER_LOG_ERROR|VC_CONTAINER_LOG_INFO; +static int32_t verbosity_input = -1, verbosity_output = -1; + +/*****************************************************************************/ +int main(int argc, char **argv) +{ + int retval = 0; + VC_CONTAINER_T *p_ctx = 0, *p_writer_ctx = 0; + VC_CONTAINER_STATUS_T status; + unsigned int i, j; + uint8_t *buffer = malloc(BUFFER_SIZE); + int64_t seek_time; + + if(container_test_parse_cmdline(argc, argv)) + goto error_silent; + + /* Set the general verbosity */ + vc_container_log_set_verbosity(0, verbosity); + + if(verbosity_input < 0) verbosity_input = verbosity; + if(verbosity_output < 0) verbosity_output = verbosity; + + /* Open a dump file if it was requested */ + if(b_dump) + { + char *psz_dump; + + if (psz_out) + { + psz_dump = strdup(psz_out); + } else { + psz_dump = strrchr(psz_in, '\\'); if(psz_dump) psz_dump++; + if(!psz_dump) {psz_dump = strrchr(psz_in, '/'); if(psz_dump) psz_dump++;} + if(!psz_dump) psz_dump = strdup(psz_in); + else psz_dump = strdup(psz_dump); + psz_dump[strlen(psz_dump)-1] = '1'; + } + dump_file = fopen(psz_dump, "wb"); + if(!dump_file) LOG_ERROR(0, "error opening dump file %s", psz_dump); + else LOG_INFO(0, "data packets will dumped to %s", psz_dump); + free(psz_dump); + if(!dump_file) goto error; + } + + /* Open a writer if an output was requested */ + if(psz_out && !b_dump) + { + vc_container_log_set_default_verbosity(verbosity_output); + p_writer_ctx = vc_container_open_writer(psz_out, &status, 0, 0); + if(!p_writer_ctx) + { + LOG_ERROR(0, "error opening file %s (%i)", psz_out, status); + goto error; + } + } + + vc_container_log_set_default_verbosity(verbosity_input); + + /* Open the container */ + if(b_client_io) + { + VC_CONTAINER_IO_T *p_io; + + LOG_INFO(0, "Using client I/O for %s", psz_in); + p_io = client_io_open(psz_in, &status); + if(!p_io) + { + LOG_ERROR(0, "error creating io for %s (%i)", psz_in, status); + goto error; + } + + p_ctx = vc_container_open_reader_with_io(p_io, psz_in, &status, 0, 0); + if(!p_ctx) + vc_container_io_close(p_io); + } + else + { + p_ctx = vc_container_open_reader(psz_in, &status, 0, 0); + } + + if(!p_ctx) + { + LOG_ERROR(0, "error opening file %s (%i)", psz_in, status); + goto error; + } + + /* Disabling tracks which are not requested and enable packetisation if requested */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[i]; + unsigned int disable = 0; + + switch(track->format->es_type) + { + case VC_CONTAINER_ES_TYPE_VIDEO: if(!b_video) disable = 1; break; + case VC_CONTAINER_ES_TYPE_AUDIO: if(!b_audio) disable = 1; break; + case VC_CONTAINER_ES_TYPE_SUBPICTURE: if(!b_subs) disable = 1; break; + default: break; + } + if(disable) + { + track->is_enabled = 0; + LOG_INFO(0, "disabling track: %i, fourcc: %4.4s", i, (char *)&track->format->codec); + } + + if(track->is_enabled && b_packetize && !(track->format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED)) + { + status = vc_container_control(p_ctx, VC_CONTAINER_CONTROL_TRACK_PACKETIZE, i, track->format->codec_variant); + if(status != VC_CONTAINER_SUCCESS) + { + LOG_ERROR(0, "packetization not supported on track: %i, fourcc: %4.4s", i, (char *)&track->format->codec); + track->is_enabled = 0; + } + } + } + + container_test_info(p_ctx, true); + if(b_info) goto end; + + if(p_writer_ctx) + { + LOG_INFO(0, "----Writer Information----"); + for(i = 0; i < p_ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = p_ctx->tracks[i]; + if(!track->is_enabled) continue; + tracks[p_writer_ctx->tracks_num].mapping = i; + LOG_INFO(0, "adding track: %i, fourcc: %4.4s", i, (char *)&track->format->codec); + status = vc_container_control(p_writer_ctx, VC_CONTAINER_CONTROL_TRACK_ADD, track->format); + if(status) + { + LOG_INFO(0, "unsupported track type (%i, %i)", status, i); + track->is_enabled = 0; /* disable track */ + } + } + if(p_writer_ctx->tracks_num) + { + status = vc_container_control(p_writer_ctx, VC_CONTAINER_CONTROL_TRACK_ADD_DONE); + if(status) LOG_INFO(0, "could not add tracks (%i)", status); + } + LOG_INFO(0, "--------------------------"); + LOG_INFO(0, ""); + } + + for(i = 0; i < seeks; i++) + { + LOG_DEBUG(0, "TEST seek to %ims", seek_offsets[i]); + seek_time = ((int64_t)(seek_offsets[i])) * 1000; + status = vc_container_seek(p_ctx, &seek_time, VC_CONTAINER_SEEK_MODE_TIME, seek_flags[i]); + LOG_DEBUG(0, "TEST seek done (%i) to %ims", status, (int)(seek_time/1000)); + } + + LOG_DEBUG(0, "TEST start reading"); + for(i = 0; !packets_num || (long)i < packets_num; i++) + { + VC_CONTAINER_PACKET_T packet = {0}; + int32_t frame_num = 0; + + status = vc_container_read(p_ctx, &packet, VC_CONTAINER_READ_FLAG_INFO); + if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST info status: %i", status); break;} + + if(packet.track < MAX_TRACKS) + { + if((packet.flags & VC_CONTAINER_PACKET_FLAG_FRAME_START)) + { + tracks[packet.track].frames++; + tracks[packet.track].frame_size = 0; + } + frame_num = tracks[packet.track].frames; + } + + tracks[packet.track].frame_size += packet.size; + + LOG_DEBUG(0, "packet info: track %i, size %i/%i/%i, pts %"PRId64", flags %x%s, num %i", + packet.track, packet.size, packet.frame_size, tracks[packet.track].frame_size, packet.pts, packet.flags, + (packet.flags & VC_CONTAINER_PACKET_FLAG_KEYFRAME) ? " (keyframe)" : "", + frame_num-1); + + if(track_num >= 0 && packet.track != (uint32_t)track_num) + { + status = vc_container_read(p_ctx, 0, VC_CONTAINER_READ_FLAG_SKIP); + if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST skip status: %i", status); break;} + continue; + } + + packet.buffer_size = BUFFER_SIZE; + packet.data = buffer; + packet.size = 0; + status = vc_container_read(p_ctx, &packet, 0); + if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST read status: %i", status); break;} + + LOG_DEBUG(0, "packet: track %i, size %i, pts %"PRId64", flags %x", packet.track, packet.size, packet.pts, packet.flags); + + if (tracks[packet.track].packets) + { + tracks[packet.track].last_dts = packet.dts; + tracks[packet.track].last_pts = packet.pts; + } else { + tracks[packet.track].first_dts = packet.dts; + tracks[packet.track].first_pts = packet.pts; + } + + if(packet.track < MAX_TRACKS) + { + tracks[packet.track].packets++; + tracks[packet.track].bytes += packet.size; + } + + if(dump_file) fwrite(packet.data, packet.size, 1, dump_file); + + if(p_writer_ctx) + { + packet.track = tracks[packet.track].mapping; + status = vc_container_write(p_writer_ctx, &packet); + if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST write status: %i", status); break;} + } + } + LOG_DEBUG(0, "TEST stop reading"); + + if(b_seek) + { + LOG_DEBUG(0, "TEST start seeking"); + for(j = 0, seek_time = 100; j < 20; j++) + { + LOG_DEBUG(0, "seeking to %ims", (int)(seek_time/1000)); + status = vc_container_seek(p_ctx, &seek_time, VC_CONTAINER_SEEK_MODE_TIME, VC_CONTAINER_SEEK_FLAG_FORWARD); + LOG_DEBUG(0, "seek done (%i) to %ims", status, (int)(seek_time/1000)); + + for(i = 0; i < 1; i++) + { + VC_CONTAINER_PACKET_T packet = {0}; + packet.buffer_size = BUFFER_SIZE; + packet.data = buffer; + + status = vc_container_read(p_ctx, &packet, 0); + if(status) LOG_DEBUG(0, "TEST read status: %i", status); + if(status == VC_CONTAINER_ERROR_EOS) break; + if(status == VC_CONTAINER_ERROR_CORRUPTED) break; + if(status == VC_CONTAINER_ERROR_FORMAT_INVALID) break; + seek_time = packet.pts + 800000; + } + } + LOG_DEBUG(0, "TEST stop seeking"); + } + + /* Output stats */ + for(i = 0; i < p_ctx->tracks_num; i++) + { + LOG_INFO(0, "track %u: read %u samples in %u packets for a total of %"PRIu64" bytes", + i, tracks[i].frames, tracks[i].packets, tracks[i].bytes); + LOG_INFO(0, "Starting at %"PRId64"us (decode at %"PRId64"us), ending at %"PRId64"us (decode at %"PRId64"us)", + tracks[i].first_pts, tracks[i].first_dts, tracks[i].last_pts, tracks[i].last_dts); + } + + end: + if(p_ctx) vc_container_close(p_ctx); + if(p_writer_ctx) + { + container_test_info(p_writer_ctx, false); + vc_container_close(p_writer_ctx); + } + if(dump_file) fclose(dump_file); + free(buffer); + +#ifdef _MSC_VER + getchar(); +#endif + + LOG_ERROR(0, "TEST ENDED (%i)", retval); + return b_errorcode ? retval : 0; + + error: + LOG_ERROR(0, "TEST FAILED"); + error_silent: + retval = -1; + goto end; +} + +static int container_test_parse_cmdline(int argc, char **argv) +{ + int i, j, k; + int32_t *p_verbosity; + + /* Parse the command line arguments */ + for(i = 1; i < argc; i++) + { + if(!argv[i]) continue; + + if(argv[i][0] != '-') + { + /* Not an option argument so will be the input URI */ + psz_in = argv[i]; + continue; + } + + /* We are now dealing with command line options */ + switch(argv[i][1]) + { + case 'i': b_info = 1; break; + case 'S': b_seek = 1; break; + case 'd': b_dump = 1; break; + case 'c': b_client_io = 1; break; + case 'v': + if(argv[i][2] == 'i') {j = 3; p_verbosity = &verbosity_input;} + else if(argv[i][2] == 'o') {j = 3; p_verbosity = &verbosity_output;} + else {j = 2; p_verbosity = &verbosity;} + *p_verbosity = VC_CONTAINER_LOG_ERROR|VC_CONTAINER_LOG_INFO; + for(k = 0; k < 2 && argv[i][j+k] == 'v'; k++) + *p_verbosity = (*p_verbosity << 1) | 1 ; + break; + case 's': + if(i+1 == argc || !argv[i+1]) goto invalid_option; + if(seeks >= MAX_SEEKS) goto invalid_option; + seek_flags[seeks] = argv[i][2] == 'f' ? VC_CONTAINER_SEEK_FLAG_FORWARD : 0; + seek_offsets[seeks] = strtol(argv[++i], 0, 0); + if(seek_offsets[seeks] < 0 || seek_offsets[seeks] == LONG_MAX) goto invalid_option; + seeks++; + break; + case 'n': + if(argv[i][2] == 'a') b_audio = 0; + else if(argv[i][2] == 'v') b_video = 0; + else if(argv[i][2] == 's') b_subs = 0; + else if(argv[i][2] == 'r') b_errorcode = 0; + else goto invalid_option; + break; + case 'e': + if(argv[i][2] == 'p') b_packetize = 1; + else goto invalid_option; + break; + case 'o': + if(i+1 == argc || !argv[i+1] || argv[i+1][0] == '-') goto invalid_option; + psz_out = argv[++i]; + break; + case 'p': + if(i+1 == argc || !argv[i+1]) goto invalid_option; + packets_num = strtol(argv[++i], 0, 0); + if(packets_num < 0 || packets_num == LONG_MAX) goto invalid_option; + break; + case 't': + if(i+1 == argc || !argv[i+1]) goto invalid_option; + track_num = strtol(argv[++i], 0, 0); + if(track_num == LONG_MIN || track_num == LONG_MAX) goto invalid_option; + break; + case 'h': goto usage; + default: goto invalid_option; + } + continue; + } + + /* Sanity check that we have at least an input uri */ + if(!psz_in) + { + LOG_ERROR(0, "missing uri argument"); + goto usage; + } + + return 0; + + invalid_option: + LOG_ERROR(0, "invalid command line option (%s)", argv[i]); + + usage: + psz_in = strrchr(argv[0], '\\'); if(psz_in) psz_in++; + if(!psz_in) {psz_in = strrchr(argv[0], '/'); if(psz_in) psz_in++;} + if(!psz_in) psz_in = argv[0]; + LOG_INFO(0, ""); + LOG_INFO(0, "usage: %s [options] uri", psz_in); + LOG_INFO(0, "options list:"); + LOG_INFO(0, " -i : only print information on the container"); + LOG_INFO(0, " -p X : read only X packets from the container"); + LOG_INFO(0, " -t X : read only packets from track X"); + LOG_INFO(0, " -s X : seek to X milliseconds before starting reading"); + LOG_INFO(0, " -sf X : seek forward to X milliseconds before starting reading"); + LOG_INFO(0, " -S : do seek testing"); + LOG_INFO(0, " -d : dump the data read from the container to files (-o to name file)"); + LOG_INFO(0, " -o uri: output to another uri (i.e. re-muxing)"); + LOG_INFO(0, " -na : disable audio"); + LOG_INFO(0, " -nv : disable video"); + LOG_INFO(0, " -ns : disable subtitles"); + LOG_INFO(0, " -nr : always return an error code of 0 (even in case of failure)"); + LOG_INFO(0, " -ep : enable packetization if data is not already packetized"); + LOG_INFO(0, " -c : use the client i/o functions"); + LOG_INFO(0, " -vxx : general verbosity level (replace xx with a number of \'v\')"); + LOG_INFO(0, " -vixx : verbosity specific to the input container"); + LOG_INFO(0, " -voxx : verbosity specific to the output container"); + LOG_INFO(0, " -h : help"); + return 1; +} + +static int container_test_info(VC_CONTAINER_T *ctx, bool b_reader) +{ + const char *name_type; + unsigned int i; + + LOG_INFO(0, ""); + if(b_reader) LOG_INFO(0, "----Reader Information----"); + else LOG_INFO(0, "----Writer Information----"); + + LOG_INFO(0, "duration: %2.2fs, size: %"PRId64, ctx->duration/1000000.0, ctx->size); + LOG_INFO(0, "capabilities: %x", ctx->capabilities); + LOG_INFO(0, ""); + + for(i = 0; i < ctx->tracks_num; i++) + { + VC_CONTAINER_TRACK_T *track = ctx->tracks[i]; + + switch(track->format->es_type) + { + case VC_CONTAINER_ES_TYPE_AUDIO: name_type = "audio"; break; + case VC_CONTAINER_ES_TYPE_VIDEO: name_type = "video"; break; + case VC_CONTAINER_ES_TYPE_SUBPICTURE: name_type = "subpicture"; break; + default: name_type = "unknown"; break; + } + + LOG_INFO(0, "track: %i, type: %s, fourcc: %4.4s", i, name_type, (char *)&track->format->codec); + LOG_INFO(0, " bitrate: %i, framed: %i, enabled: %i", track->format->bitrate, + !!(track->format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED), track->is_enabled); + LOG_INFO(0, " extra data: %i, %p", track->format->extradata_size, track->format->extradata); + switch(track->format->es_type) + { + case VC_CONTAINER_ES_TYPE_AUDIO: + LOG_INFO(0, " samplerate: %i, channels: %i, bps: %i, block align: %i", + track->format->type->audio.sample_rate, track->format->type->audio.channels, + track->format->type->audio.bits_per_sample, track->format->type->audio.block_align); + LOG_INFO(0, " gapless delay: %i gapless padding: %i", + track->format->type->audio.gap_delay, track->format->type->audio.gap_padding); + LOG_INFO(0, " language: %4.4s", track->format->language); + break; + + case VC_CONTAINER_ES_TYPE_VIDEO: + LOG_INFO(0, " width: %i, height: %i, (%i,%i,%i,%i)", + track->format->type->video.width, track->format->type->video.height, + track->format->type->video.x_offset, track->format->type->video.y_offset, + track->format->type->video.visible_width, track->format->type->video.visible_height); + LOG_INFO(0, " pixel aspect ratio: %i/%i, frame rate: %i/%i", + track->format->type->video.par_num, track->format->type->video.par_den, + track->format->type->video.frame_rate_num, track->format->type->video.frame_rate_den); + break; + + case VC_CONTAINER_ES_TYPE_SUBPICTURE: + LOG_INFO(0, " language: %4.4s, encoding: %i", track->format->language, + track->format->type->subpicture.encoding); + break; + + default: break; + } + } + + for (i = 0; i < ctx->meta_num; ++i) + { + const char *name, *value; + if (i == 0) LOG_INFO(0, ""); + name = vc_container_metadata_id_to_string(ctx->meta[i]->key); + value = ctx->meta[i]->value; + if(!name) continue; + LOG_INFO(0, "metadata(%i) : %s : %s", i, name, value); + } + + LOG_INFO(0, "--------------------------"); + LOG_INFO(0, ""); + + return 0; +} + +/***************************************************************************** + * Client I/O wrapper. Only used when the right cmd line option is passed. + *****************************************************************************/ +static VC_CONTAINER_STATUS_T client_io_close( VC_CONTAINER_IO_T *p_ctx ) +{ + FILE *fd = (FILE *)p_ctx->module; + fclose(fd); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +static size_t client_io_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size) +{ + FILE *fd = (FILE *)p_ctx->module; + size_t ret = fread(buffer, 1, size, fd); + if(ret != size) + { + /* Sanity check return value. Some platforms (e.g. Android) can return -1 */ + if( ((int)ret) < 0 ) ret = 0; + + if( feof(fd) ) p_ctx->status = VC_CONTAINER_ERROR_EOS; + else p_ctx->status = VC_CONTAINER_ERROR_FAILED; + } + LOG_DEBUG( 0, "read: %i", ret ); + return ret; +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T client_io_seek(VC_CONTAINER_IO_T *p_ctx, int64_t offset) +{ + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + FILE *fd = (FILE *)p_ctx->module; + int ret; + + //FIXME: no large file support + if (offset > (int64_t)UINT_MAX) + { + LOG_ERROR( 0, "no large file support"); + p_ctx->status = VC_CONTAINER_ERROR_EOS; + return VC_CONTAINER_ERROR_EOS; + } + + ret = fseek(fd, (long)offset, SEEK_SET); + if(ret) + { + if( feof(fd) ) status = VC_CONTAINER_ERROR_EOS; + else status = VC_CONTAINER_ERROR_FAILED; + } + + p_ctx->status = status; + return status; +} + +/*****************************************************************************/ +static VC_CONTAINER_IO_T *client_io_open( const char *psz_uri, VC_CONTAINER_STATUS_T *status ) +{ + VC_CONTAINER_IO_T *p_io; + VC_CONTAINER_IO_CAPABILITIES_T capabilities = VC_CONTAINER_IO_CAPS_NO_CACHING; + FILE *fd; + + fd = fopen(psz_uri, "rb"); + if (!fd) + { + *status = VC_CONTAINER_ERROR_URI_OPEN_FAILED; + return NULL; + } + + p_io = vc_container_io_create( psz_uri, 0, capabilities, status ); + if(!p_io) + { + LOG_ERROR(0, "error creating io (%i)", *status); + fclose(fd); + return NULL; + } + + p_io->module = (struct VC_CONTAINER_IO_MODULE_T *)fd; + p_io->pf_close = client_io_close; + p_io->pf_read = client_io_read; + p_io->pf_seek = client_io_seek; + + //FIXME: no large file support + fseek(fd, 0, SEEK_END); + p_io->size = ftell(fd); + fseek(fd, 0, SEEK_SET); + + *status = VC_CONTAINER_SUCCESS; + return p_io; +} diff --git a/containers/test/test_bits.c b/containers/test/test_bits.c new file mode 100644 index 0000000..7ce1651 --- /dev/null +++ b/containers/test/test_bits.c @@ -0,0 +1,600 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#define BITS_LOG_INDENT(ctx) indent_level +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_bits.h" + +uint32_t indent_level; + +/** Bit stream containing the values 0 to 10, with each value in that many bits. + * At the end there is one further zero bit before the end of the stream. */ +static uint8_t bits_0_to_10[] = { + 0xCD, 0x0A, 0x30, 0x70, 0x80, 0x48, 0x14 +}; + +/** Bit stream containing the values 0 to 10, encoded using Exp-Golomb. + * At the end there is one further one bit before the end of the stream. */ +static uint8_t exp_golomb_0_to_10[] = { + 0xA6, 0x42, 0x98, 0xE2, 0x04, 0x8A, 0x17 +}; + +/** Array of signed values for the Exp-Golomb encoding of each index. */ +static int32_t exp_golomb_values[] = { + 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5 +}; + +/** Bit stream containing two large 32-bit values, encoded using Exp-Golomb. */ +static uint8_t exp_golomb_large[] = { + 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 +}; + +/** Bit stream containing a 33-bit value, encoded using Exp-Golomb. */ +static uint8_t exp_golomb_oversize[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80 +}; + + +static const char *plural_ext(uint32_t val) +{ + return (val == 1) ? "" : "s"; +} + +static int test_reset_and_available(void) +{ + VC_CONTAINER_BITS_T bit_stream; + int error_count = 0; + + LOG_DEBUG(NULL, "Testing vc_container_bits_reset and vc_container_bits_available"); + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + + if (!BITS_AVAILABLE(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Expected initialised stream to contain bits"); + error_count++; + } + + BITS_RESET(NULL, &bit_stream); + + if (BITS_AVAILABLE(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Expected reset stream not to contain bits"); + error_count++; + } + + return error_count; +} + +static int test_read_u32(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii, value; + int error_count = 0; + + LOG_DEBUG(NULL, "Testing vc_container_bits_get_u32"); + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + + for (ii = 0; ii < 11; ii++) + { + value = BITS_READ_U32(NULL, &bit_stream, ii, "test_read_u32"); + if (value != ii) + { + LOG_ERROR(NULL, "Expected %u, got %u", ii, value); + error_count++; + } + } + + value = BITS_READ_U32(NULL, &bit_stream, 1, "Final bit"); + if (!BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Failed to get final bit"); + error_count++; + } + value = BITS_READ_U32(NULL, &bit_stream, 1, "Beyond final bit"); + if (BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Unexpectedly succeeded reading beyond expected end of stream"); + error_count++; + } + + return error_count; +} + +static int test_skip(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii; + int error_count = 0; + uint32_t last_bits_left, bits_left; + + LOG_DEBUG(NULL, "Testing vc_container_bits_skip"); + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + + last_bits_left = BITS_AVAILABLE(NULL, &bit_stream); + for (ii = 0; ii < 11; ii++) + { + BITS_SKIP(NULL, &bit_stream, ii, "test_skip"); + bits_left = BITS_AVAILABLE(NULL, &bit_stream); + if (bits_left + ii != last_bits_left) + { + int32_t actual = last_bits_left - bits_left; + LOG_ERROR(NULL, "Tried to skip %u bit%s, actually skipped %d bit%s", + ii, plural_ext(ii), actual, plural_ext(actual)); + error_count++; + } + last_bits_left = bits_left; + } + + BITS_SKIP(NULL, &bit_stream, 1, "Final bit"); + if (!BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Failed to skip final bit"); + error_count++; + } + if (BITS_AVAILABLE(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "End of stream not reached by skipping"); + error_count++; + } + + BITS_SKIP(NULL, &bit_stream, 1, "Beyond final bit"); + if (BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Unexpectedly succeeded skipping beyond expected end of stream"); + error_count++; + } + return error_count; +} + +static int test_ptr_and_skip_bytes(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii; + const uint8_t *expected_ptr; + int error_count = 0; + uint32_t last_bytes_left, bytes_left; + + LOG_DEBUG(NULL, "Testing vc_container_bits_current_pointer, vc_container_bits_skip_bytes and vc_container_bits_bytes_available"); + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + + last_bytes_left = BITS_BYTES_AVAILABLE(NULL, &bit_stream); + if (last_bytes_left != countof(bits_0_to_10)) + { + LOG_ERROR(NULL, "Expected bytes available to initially match given size"); + error_count++; + } + + if (BITS_CURRENT_POINTER(NULL, &bit_stream) != bits_0_to_10) + { + LOG_ERROR(NULL, "Expected initial current pointer to match original buffer"); + error_count++; + } + + expected_ptr = bits_0_to_10; + for (ii = 0; ii < 4; ii++) + { + BITS_SKIP_BYTES(NULL, &bit_stream, ii, "test_ptr_and_skip_bytes"); + + expected_ptr += ii; + if (BITS_CURRENT_POINTER(NULL, &bit_stream) != expected_ptr) + { + LOG_ERROR(NULL, "Expected current pointer to have moved by %u byte%s", ii, plural_ext(ii)); + error_count++; + } + + bytes_left = BITS_BYTES_AVAILABLE(NULL, &bit_stream); + if (bytes_left + ii != last_bytes_left) + { + int32_t actual = last_bytes_left - bytes_left; + LOG_ERROR(NULL, "Tried to skip %u byte%s, actually skipped %d byte%s", + ii, plural_ext(ii), actual, plural_ext(actual)); + error_count++; + } + + last_bytes_left = bytes_left; + } + + if (!bytes_left) + { + LOG_ERROR(NULL, "Reached end of stream too soon"); + error_count++; + } + if (!BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Expected stream to be valid"); + error_count++; + } + + BITS_SKIP_BYTES(NULL, &bit_stream, bytes_left + 1, "Beyond end of stream"); + if (BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Unexpectedly succeeded skipping bytes beyond end of stream"); + error_count++; + } + if (BITS_BYTES_AVAILABLE(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Expected stream to have been reset"); + error_count++; + } + + return error_count; +} + +static int test_reduce_bytes(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii; + int error_count = 0; + uint32_t last_bytes_left, bytes_left; + + LOG_DEBUG(NULL, "Testing vc_container_bits_reduce_bytes"); + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + + last_bytes_left = BITS_BYTES_AVAILABLE(NULL, &bit_stream); + if (last_bytes_left != countof(bits_0_to_10)) + { + LOG_ERROR(NULL, "Expected bytes available to initially match given size"); + error_count++; + } + + if (BITS_CURRENT_POINTER(NULL, &bit_stream) != bits_0_to_10) + { + LOG_ERROR(NULL, "Expected initial current pointer to match original buffer"); + error_count++; + } + + for (ii = 0; ii < 4; ii++) + { + BITS_REDUCE_BYTES(NULL, &bit_stream, ii, "test_reduce_bytes"); + + if (BITS_CURRENT_POINTER(NULL, &bit_stream) != bits_0_to_10) + { + LOG_ERROR(NULL, "Did not expect current pointer to have moved"); + error_count++; + } + + bytes_left = BITS_BYTES_AVAILABLE(NULL, &bit_stream); + if (bytes_left + ii != last_bytes_left) + { + int32_t actual = last_bytes_left - bytes_left; + LOG_ERROR(NULL, "Tried to reduce by %u byte%s, actually reduced by %d byte%s", + ii, plural_ext(ii), actual, plural_ext(actual)); + error_count++; + } + + last_bytes_left = bytes_left; + } + + if (!bytes_left) + { + LOG_ERROR(NULL, "Reached end of stream too soon"); + error_count++; + } + if (!BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Expected stream to be valid"); + error_count++; + } + + BITS_REDUCE_BYTES(NULL, &bit_stream, bytes_left + 1, "Reducing an empty stream"); + if (BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Unexpectedly succeeded reducing by too many bytes"); + error_count++; + } + if (BITS_AVAILABLE(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Expected stream to have been reset"); + error_count++; + } + + return error_count; +} + +static int test_copy_bytes(void) +{ + VC_CONTAINER_BITS_T bit_stream; + int error_count = 0; + uint8_t buffer[countof(bits_0_to_10)]; + uint32_t ii; + + LOG_DEBUG(NULL, "Testing vc_container_bits_copy_bytes"); + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + memset(buffer, 0, sizeof(buffer)); + + /* Copy whole buffer in one go */ + BITS_COPY_BYTES(NULL, &bit_stream, countof(buffer), buffer, "Copy whole buffer"); + + if (!BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Failed to copy the whole buffer"); + error_count++; + } + + if (memcmp(buffer, bits_0_to_10, countof(bits_0_to_10)) != 0) + { + LOG_ERROR(NULL, "Single copy doesn't match original"); + error_count++; + } + + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + memset(buffer, 0, sizeof(buffer)); + + /* Copy whole buffer one byte at a time */ + for (ii = 0; ii < countof(bits_0_to_10); ii++) + { + BITS_COPY_BYTES(NULL, &bit_stream, 1, buffer + ii, "Copy buffer piecemeal"); + } + + if (!BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Failed to copy the buffer piecemeal"); + error_count++; + } + + if (memcmp(buffer, bits_0_to_10, countof(bits_0_to_10)) != 0) + { + LOG_ERROR(NULL, "Multiple copy doesn't match original"); + error_count++; + } + + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + memset(buffer, 0, sizeof(buffer)); + + /* Copy part of buffer */ + BITS_SKIP_BYTES(NULL, &bit_stream, 1, "Copy part of buffer"); + BITS_REDUCE_BYTES(NULL, &bit_stream, 1, "Copy part of buffer"); + BITS_COPY_BYTES(NULL, &bit_stream, countof(buffer) - 2, buffer, "Copy part of buffer"); + + if (!BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Failed to copy part of buffer"); + error_count++; + } + + if (memcmp(buffer, bits_0_to_10 + 1, countof(bits_0_to_10) - 2) != 0) + { + LOG_ERROR(NULL, "Partial copy doesn't match original"); + error_count++; + } + + return error_count; +} + +static int test_skip_exp_golomb(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii; + int error_count = 0; + + LOG_DEBUG(NULL, "Testing vc_container_bits_skip_exp_golomb"); + BITS_INIT(NULL, &bit_stream, exp_golomb_0_to_10, countof(exp_golomb_0_to_10)); + + for (ii = 0; ii < 12; ii++) + { + BITS_SKIP_EXP(NULL, &bit_stream, "test_skip_exp_golomb"); + } + + if (!BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Failed to skip through buffer"); + error_count++; + } + + BITS_SKIP_EXP(NULL, &bit_stream, "Skip beyond end of stream"); + if (BITS_VALID(NULL, &bit_stream)) + { + LOG_ERROR(NULL, "Unexpectedly succeeded skipping beyond expected end of stream"); + error_count++; + } + + return error_count; +} + +static int test_read_u32_exp_golomb(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii, value; + int error_count = 0; + + LOG_DEBUG(NULL, "Testing vc_container_bits_get_u32_exp_golomb"); + BITS_INIT(NULL, &bit_stream, exp_golomb_0_to_10, countof(exp_golomb_0_to_10)); + + for (ii = 0; ii < 11; ii++) + { + value = BITS_READ_U32_EXP(NULL, &bit_stream, "test_read_u32_exp_golomb"); + if (value != ii) + { + LOG_ERROR(NULL, "Expected %u, got %u", ii, value); + error_count++; + } + } + + value = BITS_READ_U32(NULL, &bit_stream, 1, "Final bit"); + if (!BITS_VALID(NULL, &bit_stream) || !value) + { + LOG_ERROR(NULL, "Failed to get final bit (expected a 1)"); + error_count++; + } + value = BITS_READ_U32_EXP(NULL, &bit_stream, "Beyond end of stream"); + if (BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Unexpectedly succeeded reading beyond expected end of stream"); + error_count++; + } + + /* Test getting two large (32 bit) Exp-Golomb values */ + BITS_INIT(NULL, &bit_stream, exp_golomb_large, countof(exp_golomb_large)); + + value = BITS_READ_U32_EXP(NULL, &bit_stream, "Second largest 32-bit value"); + if (value != 0xFFFFFFFE) + { + LOG_ERROR(NULL, "Failed to get second largest 32-bit value"); + error_count++; + } + + value = BITS_READ_U32_EXP(NULL, &bit_stream, "Largest 32-bit value"); + if (value != 0xFFFFFFFF) + { + LOG_ERROR(NULL, "Failed to get largest 32-bit value"); + error_count++; + } + + /* Test getting an oversize (33 bit) Exp-Golomb value */ + BITS_INIT(NULL, &bit_stream, exp_golomb_oversize, countof(exp_golomb_oversize)); + + value = BITS_READ_U32_EXP(NULL, &bit_stream, "Unsigned 33-bit value"); + if (BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Unexpectedly got 33-bit value: %u", value); + error_count++; + } + + return error_count; +} + +static int test_read_s32_exp_golomb(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii; + int32_t value; + int error_count = 0; + + LOG_DEBUG(NULL, "Testing vc_container_bits_get_s32_exp_golomb"); + BITS_INIT(NULL, &bit_stream, exp_golomb_0_to_10, countof(exp_golomb_0_to_10)); + + for (ii = 0; ii < 11; ii++) + { + value = BITS_READ_S32_EXP(NULL, &bit_stream, "test_read_s32_exp_golomb"); + if (value != exp_golomb_values[ii]) + { + LOG_ERROR(NULL, "Expected %u, got %u", ii, value); + error_count++; + } + } + + value = BITS_READ_S32_EXP(NULL, &bit_stream, "Final bit"); + if (!BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Failed to get final Exp-Golomb value (expected a zero)"); + error_count++; + } + value = BITS_READ_S32_EXP(NULL, &bit_stream, "Beyond final bit"); + if (BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Unexpectedly succeeded reading beyond expected end of stream"); + error_count++; + } + + /* Test getting two large (32 bit) Exp-Golomb values */ + BITS_INIT(NULL, &bit_stream, exp_golomb_large, countof(exp_golomb_large)); + + value = BITS_READ_S32_EXP(NULL, &bit_stream, "Largest signed 32-bit value"); + if (!BITS_VALID(NULL, &bit_stream) || value != -0x7FFFFFFF) + { + LOG_ERROR(NULL, "Failed to get largest signed 32-bit value: %d", value); + error_count++; + } + + value = BITS_READ_S32_EXP(NULL, &bit_stream, "Just too large signed 33-bit value"); + if (BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Unexpectedly got slightly too large signed 32-bit value: %d", value); + error_count++; + } + + /* Test getting an oversize (33 bit) Exp-Golomb value */ + BITS_INIT(NULL, &bit_stream, exp_golomb_oversize, countof(exp_golomb_oversize)); + + value = BITS_READ_S32_EXP(NULL, &bit_stream, "Larger signed 33-bit value"); + if (BITS_VALID(NULL, &bit_stream) || value) + { + LOG_ERROR(NULL, "Unexpectedly got signed 33-bit value: %d", value); + error_count++; + } + + return error_count; +} + +#ifdef ENABLE_CONTAINERS_LOG_FORMAT +static int test_indentation(void) +{ + VC_CONTAINER_BITS_T bit_stream; + uint32_t ii; + + LOG_DEBUG(NULL, "Testing logging indentation"); + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + + for (ii = 0; ii < 11; ii++) + { + indent_level = ii; + BITS_READ_U32(NULL, &bit_stream, ii, "test_indentation (unit)"); + } + + BITS_INIT(NULL, &bit_stream, bits_0_to_10, countof(bits_0_to_10)); + + for (ii = 0; ii < 11; ii++) + { + indent_level = ii * 10; + BITS_READ_U32(NULL, &bit_stream, ii, "test_indentation (tens)"); + } + return 0; +} +#endif + +int main(int argc, char **argv) +{ + int error_count = 0; + + VC_CONTAINER_PARAM_UNUSED(argc); + VC_CONTAINER_PARAM_UNUSED(argv); + + error_count += test_reset_and_available(); + error_count += test_read_u32(); + error_count += test_skip(); + error_count += test_ptr_and_skip_bytes(); + error_count += test_reduce_bytes(); + error_count += test_copy_bytes(); + error_count += test_skip_exp_golomb(); + error_count += test_read_u32_exp_golomb(); + error_count += test_read_s32_exp_golomb(); +#ifdef ENABLE_CONTAINERS_LOG_FORMAT + error_count += test_indentation(); +#endif + + if (error_count) + { + LOG_ERROR(NULL, "*** %d errors reported", error_count); + getchar(); + } + + return error_count; +} diff --git a/containers/test/test_uri.c b/containers/test/test_uri.c new file mode 100644 index 0000000..0586182 --- /dev/null +++ b/containers/test/test_uri.c @@ -0,0 +1,824 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "containers/containers.h" +#include "containers/core/containers_common.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_uri.h" + +#define TEST_CHAR '.' +#define TEST_STRING "test" +#define TEST_NAME "name" +#define TEST_VALUE "value" + +#define ARRAY_SIZE(X) (sizeof(X)/sizeof(*(X))) + +struct +{ + const char *before; + const char *after; +} test_parse_uris[] = { + {"", NULL}, + {"C:\\test\\filename", NULL}, + {"/usr/local/nix/filename", NULL}, + {"scheme-only:", NULL}, + {"scheme:/and/path", NULL}, + {"scheme:/and/path#with_fragment", NULL}, + {"scheme:/and/path?query=true", NULL}, + {"scheme:/and/path?multiple+queries=true&more=false", NULL}, + {"scheme:/and/path?multiple+queries=true&more=false#and+a+fragment,+too", NULL}, + {"scheme:C:\\Windows\\path", "scheme:C:%5CWindows%5Cpath"}, + {"scheme:C:\\Windows\\path#with_fragment", "scheme:C:%5CWindows%5Cpath#with_fragment"}, + {"scheme:C:\\Windows\\path?query=true", "scheme:C:%5CWindows%5Cpath?query=true"}, + {"scheme:C:\\Windows\\path?query#and+fragment", "scheme:C:%5CWindows%5Cpath?query#and+fragment"}, + {"scheme://just.host", NULL}, + {"scheme://host/and/path", NULL}, + {"scheme://host:port", NULL}, + {"scheme://host:port/and/path", NULL}, + {"scheme://127.0.0.1:port/and/path", NULL}, + {"scheme://userinfo@host:port/and/path?query#fragment", NULL}, + {"HTTP://www.EXAMPLE.com/", "http://www.example.com/"}, + {"%48%54%54%50://%54%45%53%54/", "http://test/"}, + {"scheme:esc%", "scheme:esc%25"}, + {"scheme:esc%%", "scheme:esc%25%25"}, + {"scheme:esc%%%", "scheme:esc"}, + {"scheme:esc%1%", "scheme:esc%10"}, + {"scheme:esc%%1", "scheme:esc%01"}, + {"s+-.1234567890:", NULL}, + {"scheme:///", NULL}, + {"scheme://:just_port", NULL}, + {"scheme://:port/and/path", NULL}, + {"scheme://just_userinfo@", NULL}, + {"scheme://userinfo@/and/path", NULL}, + {"scheme://userinfo@:port/and/path", NULL}, + {"%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f:", "%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F:"}, + {"%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%5F%60%7B%7C%7D%7E%7F:", "%20%21%22%23%24%25%26%27%28%29%2A+%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~%7F:"}, + {"scheme://%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%5F%60%7B%7C%7D%7E%7F@", "scheme://%20!%22%23$%25&'()*+,-.%2F:;%3C=%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~%7F@"}, + {"scheme://%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%5F%60%7B%7C%7D%7E%7F", "scheme://%20!%22%23$%25&'()*+,-.%2F:;%3C=%3E%3F%40[%5C]%5E_%60%7B%7C%7D~%7F"}, + {"scheme://:%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%5F%60%7B%7C%7D%7E%7F", "scheme://:%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~%7F"}, + {"scheme:///%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%5F%60%7B%7C%7D%7E%7F", "scheme:///%20!%22%23$%25&'()*+,-./:;%3C=%3E%3F@%5B%5C%5D%5E_%60%7B%7C%7D~%7F"}, + {"scheme://?%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%5F%60%7B%7C%7D%7E%7F", "scheme://?%20!%22%23$%25&'()*+,-./:;%3C=%3E?@%5B%5C%5D%5E_%60%7B%7C%7D~%7F"}, + {"scheme://#%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%5F%60%7B%7C%7D%7E%7F", "scheme://#%20!%22%23$%25&'()*+,-./:;%3C=%3E?@%5B%5C%5D%5E_%60%7B%7C%7D~%7F"}, + {"scheme://[v6.1234:5678]/", NULL}, + {"scheme://[::1]/", NULL}, + {"scheme://[1234:5678:90ab:cdef:1234:5678:90ab:cdef]/", NULL}, + {"scheme://[::1]:1/", NULL}, + {"scheme://?", "scheme://"}, + {"scheme://?#", "scheme://#"}, + {"scheme://?q&&;&n=&=v&=&n=v", "scheme://?q&n=&n=v"}, + {"ldap://[2001:db8::7]/c=GB?objectClass?one", NULL}, + {"mailto:John.Doe@example.com", NULL}, + {"news:comp.infosystems.www.servers.unix", NULL}, + {"tel:+1-816-555-1212", NULL}, + {"urn:oasis:names:specification:docbook:dtd:xml:4.1.2", NULL}, +}; + +typedef struct build_uri_tag +{ + const char *expected_uri; + const char *scheme; + const char *userinfo; + const char *host; + const char *port; + const char *path; + const char *fragment; + const char **queries; /* Held as name then value. NULL values allowed. NULL name terminates list */ +} BUILD_URI_T; + +/* Test query lists */ +const char *no_queries[] = { NULL }; +const char *query_true[] = { "query", "true", NULL }; +const char *multiple_queries[] = { "multiple+queries", "true", "more", "false", NULL }; +const char *just_query[] = {"query", NULL, NULL}; +const char *complex_query[] = { "q", NULL, "n", "", "n", "v", NULL }; +const char *objectClass_one[] = { "objectClass?one", NULL, NULL }; + +BUILD_URI_T test_build_uris[] = { + {"", NULL, NULL, NULL, NULL, NULL, NULL, no_queries }, + {"C:\\test\\filename", NULL, NULL, NULL, NULL, "C:\\test\\filename", NULL, no_queries}, + {"/usr/local/nix/filename", NULL, NULL, NULL, NULL, "/usr/local/nix/filename", NULL, no_queries}, + {"scheme-only:", "scheme-only", NULL, NULL, NULL, NULL, NULL, no_queries}, + {"scheme:/and/path", "scheme", NULL, NULL, NULL, "/and/path", NULL, no_queries}, + {"scheme:/and/path#with_fragment", "scheme", NULL, NULL, NULL, "/and/path", "with_fragment", no_queries}, + {"scheme:/and/path?query=true", "scheme", NULL, NULL, NULL, "/and/path", NULL, query_true}, + {"scheme:/and/path?multiple+queries=true&more=false", "scheme", NULL, NULL, NULL, "/and/path", NULL, multiple_queries}, + {"scheme:/and/path?multiple+queries=true&more=false#and+a+fragment,+too", "scheme", NULL, NULL, NULL, "/and/path", "and+a+fragment,+too", multiple_queries}, + {"scheme://just.host", "scheme", NULL, "just.host", NULL, NULL, NULL, no_queries}, + {"scheme://host/and/path", "scheme", NULL, "host", NULL, "/and/path", NULL, no_queries}, + {"scheme://host:port", "scheme", NULL, "host", "port", NULL, NULL, no_queries}, + {"scheme://host:port/and/path", "scheme", NULL, "host", "port", "/and/path", NULL, no_queries}, + {"scheme://127.0.0.1:port/and/path", "scheme", NULL, "127.0.0.1", "port", "/and/path", NULL, no_queries}, + {"scheme://userinfo@host:port/and/path?query#fragment", "scheme", "userinfo", "host", "port", "/and/path", "fragment", just_query}, + {"scheme:///", "scheme", NULL, "", NULL, "/", NULL, no_queries }, + {"scheme://:just_port", "scheme", NULL, "", "just_port", NULL, NULL, no_queries }, + {"scheme://:port/and/path", "scheme", NULL, "", "port", "/and/path", NULL, no_queries }, + {"scheme://just_userinfo@", "scheme", "just_userinfo", "", NULL, NULL, NULL, no_queries }, + {"scheme://userinfo@/and/path", "scheme", "userinfo", "", NULL, "/and/path", NULL, no_queries }, + {"scheme://userinfo@:port/and/path", "scheme", "userinfo", "", "port", "/and/path", NULL, no_queries }, + {"scheme://", "scheme", NULL, "", NULL, NULL, NULL, no_queries }, + {"scheme://#", "scheme", NULL, "", NULL, NULL, "", no_queries }, + {"scheme://?q&n=&n=v", "scheme", NULL, "", NULL, NULL, NULL, complex_query}, + {"ldap://[2001:db8::7]/c=GB?objectClass?one", "ldap", NULL, "[2001:db8::7]", NULL, "/c=GB", NULL, objectClass_one}, + {"mailto:John.Doe@example.com", "mailto", NULL, NULL, NULL, "John.Doe@example.com", NULL, no_queries }, + {"news:comp.infosystems.www.servers.unix", "news", NULL, NULL, NULL, "comp.infosystems.www.servers.unix", NULL, no_queries }, + {"tel:+1-816-555-1212", "tel", NULL, NULL, NULL, "+1-816-555-1212", NULL, no_queries }, + {"urn:oasis:names:specification:docbook:dtd:xml:4.1.2", "urn", NULL, NULL, NULL, "oasis:names:specification:docbook:dtd:xml:4.1.2", NULL, no_queries }, +}; + +typedef struct merge_uri_tag +{ + const char *base; + const char *relative; + const char *merged; +} MERGE_URI_T; + +MERGE_URI_T test_merge_uris[] = { + /* Normal examples */ + { "http://a/b/c/d;p?q#f", "ftp:h", "ftp:h" }, + { "http://a/b/c/d;p?q#f", "g", "http://a/b/c/g" }, + { "http://a/b/c/d;p?q#f", "./g", "http://a/b/c/g" }, + { "http://a/b/c/d;p?q#f", "g/", "http://a/b/c/g/" }, + { "http://a/b/c/d;p?q#f", "/g", "http://a/g" }, + { "http://a/b/c/d;p?q#f", "//g", "http://g" }, + { "http://a/b/c/d;p?q#f", "?y", "http://a/b/c/d;p?y" }, + { "http://a/b/c/d;p?q#f", "g?y", "http://a/b/c/g?y" }, + { "http://a/b/c/d;p?q#f", "g?y/./x", "http://a/b/c/g?y/./x" }, + { "http://a/b/c/d;p?q#f", "#s", "http://a/b/c/d;p?q#s" }, + { "http://a/b/c/d;p?q#f", "g#s", "http://a/b/c/g#s" }, + { "http://a/b/c/d;p?q#f", "g#s/./x", "http://a/b/c/g#s/./x" }, + { "http://a/b/c/d;p?q#f", "g?y#s", "http://a/b/c/g?y#s" }, + { "http://a/b/c/d;p?q#f", ";x", "http://a/b/c/d;x" }, + { "http://a/b/c/d;p?q#f", "g;x", "http://a/b/c/g;x" }, + { "http://a/b/c/d;p?q#f", "g;x?y#s", "http://a/b/c/g;x?y#s" }, + { "http://a/b/c/d;p?q#f", ".", "http://a/b/c/" }, + { "http://a/b/c/d;p?q#f", "./", "http://a/b/c/" }, + { "http://a/b/c/d;p?q#f", "..", "http://a/b/" }, + { "http://a/b/c/d;p?q#f", "../", "http://a/b/" }, + { "http://a/b/c/d;p?q#f", "../g", "http://a/b/g" }, + { "http://a/b/c/d;p?q#f", "../..", "http://a/" }, + { "http://a/b/c/d;p?q#f", "../../", "http://a/" }, + { "http://a/b/c/d;p?q#f", "../../g", "http://a/g" }, + /* Normal examples, without base network info */ + { "http:/b/c/d;p?q#f", "g", "http:/b/c/g" }, + { "http:/b/c/d;p?q#f", "./g", "http:/b/c/g" }, + { "http:/b/c/d;p?q#f", "g/", "http:/b/c/g/" }, + { "http:/b/c/d;p?q#f", "/g", "http:/g" }, + { "http:/b/c/d;p?q#f", "//g", "http://g" }, + { "http:/b/c/d;p?q#f", "?y", "http:/b/c/d;p?y" }, + { "http:/b/c/d;p?q#f", "g?y", "http:/b/c/g?y" }, + { "http:/b/c/d;p?q#f", "g?y/./x", "http:/b/c/g?y/./x" }, + { "http:/b/c/d;p?q#f", "#s", "http:/b/c/d;p?q#s" }, + { "http:/b/c/d;p?q#f", "g#s", "http:/b/c/g#s" }, + { "http:/b/c/d;p?q#f", "g#s/./x", "http:/b/c/g#s/./x" }, + { "http:/b/c/d;p?q#f", "g?y#s", "http:/b/c/g?y#s" }, + { "http:/b/c/d;p?q#f", ";x", "http:/b/c/d;x" }, + { "http:/b/c/d;p?q#f", "g;x", "http:/b/c/g;x" }, + { "http:/b/c/d;p?q#f", "g;x?y#s", "http:/b/c/g;x?y#s" }, + { "http:/b/c/d;p?q#f", ".", "http:/b/c/" }, + { "http:/b/c/d;p?q#f", "./", "http:/b/c/" }, + { "http:/b/c/d;p?q#f", "..", "http:/b/" }, + { "http:/b/c/d;p?q#f", "../", "http:/b/" }, + { "http:/b/c/d;p?q#f", "../g", "http:/b/g" }, + { "http:/b/c/d;p?q#f", "../..", "http:/" }, + { "http:/b/c/d;p?q#f", "../../", "http:/" }, + { "http:/b/c/d;p?q#f", "../../g", "http:/g" }, + /* Normal examples, without base network info or path root */ + { "http:b/c/d;p?q#f", "g", "http:b/c/g" }, + { "http:b/c/d;p?q#f", "./g", "http:b/c/g" }, + { "http:b/c/d;p?q#f", "g/", "http:b/c/g/" }, + { "http:b/c/d;p?q#f", "/g", "http:/g" }, + { "http:b/c/d;p?q#f", "//g", "http://g" }, + { "http:b/c/d;p?q#f", "?y", "http:b/c/d;p?y" }, + { "http:b/c/d;p?q#f", "g?y", "http:b/c/g?y" }, + { "http:b/c/d;p?q#f", "g?y/./x", "http:b/c/g?y/./x" }, + { "http:b/c/d;p?q#f", "#s", "http:b/c/d;p?q#s" }, + { "http:b/c/d;p?q#f", "g#s", "http:b/c/g#s" }, + { "http:b/c/d;p?q#f", "g#s/./x", "http:b/c/g#s/./x" }, + { "http:b/c/d;p?q#f", "g?y#s", "http:b/c/g?y#s" }, + { "http:b/c/d;p?q#f", ";x", "http:b/c/d;x" }, + { "http:b/c/d;p?q#f", "g;x", "http:b/c/g;x" }, + { "http:b/c/d;p?q#f", "g;x?y#s", "http:b/c/g;x?y#s" }, + { "http:b/c/d;p?q#f", ".", "http:b/c/" }, + { "http:b/c/d;p?q#f", "./", "http:b/c/" }, + { "http:b/c/d;p?q#f", "..", "http:b/" }, + { "http:b/c/d;p?q#f", "../", "http:b/" }, + { "http:b/c/d;p?q#f", "../g", "http:b/g" }, + { "http:b/c/d;p?q#f", "../..", "http:" }, + { "http:b/c/d;p?q#f", "../../", "http:" }, + { "http:b/c/d;p?q#f", "../../g", "http:g" }, + /* Normal examples, without base path */ + { "http://a?q#f", "g", "http://a/g" }, + { "http://a?q#f", "./g", "http://a/g" }, + { "http://a?q#f", "g/", "http://a/g/" }, + { "http://a?q#f", "/g", "http://a/g" }, + { "http://a?q#f", "//g", "http://g" }, + { "http://a?q#f", "?y", "http://a?y" }, + { "http://a?q#f", "g?y", "http://a/g?y" }, + { "http://a?q#f", "g?y/./x", "http://a/g?y/./x" }, + { "http://a?q#f", "#s", "http://a?q#s" }, + { "http://a?q#f", "g#s", "http://a/g#s" }, + { "http://a?q#f", "g#s/./x", "http://a/g#s/./x" }, + { "http://a?q#f", "g?y#s", "http://a/g?y#s" }, + { "http://a?q#f", ";x", "http://a/;x" }, + { "http://a?q#f", "g;x", "http://a/g;x" }, + { "http://a?q#f", "g;x?y#s", "http://a/g;x?y#s" }, + { "http://a?q#f", ".", "http://a/" }, + { "http://a?q#f", "./", "http://a/" }, + /* Normal examples, without base network info or path */ + { "http:?q#f", "g", "http:g" }, + { "http:?q#f", "./g", "http:g" }, + { "http:?q#f", "g/", "http:g/" }, + { "http:?q#f", "/g", "http:/g" }, + { "http:?q#f", "//g", "http://g" }, + { "http:?q#f", "?y", "http:?y" }, + { "http:?q#f", "g?y", "http:g?y" }, + { "http:?q#f", "g?y/./x", "http:g?y/./x" }, + { "http:?q#f", "#s", "http:?q#s" }, + { "http:?q#f", "g#s", "http:g#s" }, + { "http:?q#f", "g#s/./x", "http:g#s/./x" }, + { "http:?q#f", "g?y#s", "http:g?y#s" }, + { "http:?q#f", ";x", "http:;x" }, + { "http:?q#f", "g;x", "http:g;x" }, + { "http:?q#f", "g;x?y#s", "http:g;x?y#s" }, + /* Abnormal (but valid) examples */ + { "http://a/b/c/d;p?q#f", "../../../g", "http://a/../g" }, + { "http://a/b/c/d;p?q#f", "../../../../g", "http://a/../../g" }, + { "http://a/b/c/d;p?q#f", "/./g", "http://a/./g" }, + { "http://a/b/c/d;p?q#f", "/../g", "http://a/../g" }, + { "http://a/b/c/d;p?q#f", "g.", "http://a/b/c/g." }, + { "http://a/b/c/d;p?q#f", ".g", "http://a/b/c/.g" }, + { "http://a/b/c/d;p?q#f", "g..", "http://a/b/c/g.." }, + { "http://a/b/c/d;p?q#f", "..g", "http://a/b/c/..g" }, + { "http://a/b/c/d;p?q#f", "./../g", "http://a/b/g" }, + { "http://a/b/c/d;p?q#f", "./g/.", "http://a/b/c/g/" }, + { "http://a/b/c/d;p?q#f", "g/./h", "http://a/b/c/g/h" }, + { "http://a/b/c/d;p?q#f", "g/../h", "http://a/b/c/h" }, + { "http://a/b/c/d;p?q#f", "./g:h", "http://a/b/c/g:h" }, + { "http://a/b/c/d;p?q#f", "g/..", "http://a/b/c/" }, + /* Abnormal examples without base path */ + { "http://a?q#f", "../g", "http://a/../g" }, + { "http://a?q#f", "./../g", "http://a/../g" }, + /* Abnormal examples without base network info or path */ + { "http:?q#f", "../g", "http:../g" }, + { "http:?q#f", "./../g", "http:../g" }, + { "http:?q#f", ".", "http:" }, + { "http:?q#f", "./", "http:" }, +}; + + +/** Dump a URI structure to the log. + * + * \param uri URI structure to be dumped. */ +static void dump_uri(VC_URI_PARTS_T *uri) +{ + const char *str; + uint32_t query_count, ii; + + str = vc_uri_scheme(uri); + if (str) + LOG_DEBUG(NULL, "Scheme: <%s>", str); + + str = vc_uri_userinfo(uri); + if (str) + LOG_DEBUG(NULL, "Userinfo: <%s>", str); + + str = vc_uri_host(uri); + if (str) + LOG_DEBUG(NULL, "Host: <%s>", str); + + str = vc_uri_port(uri); + if (str) + LOG_DEBUG(NULL, "Port: <%s>", str); + + str = vc_uri_path(uri); + if (str) + LOG_DEBUG(NULL, "Path: <%s>", str); + + query_count = vc_uri_num_queries(uri); + for (ii = 0; ii < query_count; ii++) + { + const char *value; + + vc_uri_query(uri, ii, &str, &value); + if (str) + { + if (value) + LOG_DEBUG(NULL, "Query %d: <%s>=<%s>", ii, str, value); + else + LOG_DEBUG(NULL, "Query %d: <%s>", ii, str); + } + } + + str = vc_uri_fragment(uri); + if (str) + LOG_DEBUG(NULL, "Fragment: <%s>", str); +} + +static int check_uri(VC_URI_PARTS_T *uri, const char *expected) +{ + uint32_t built_len; + char *built; + + built_len = vc_uri_build(uri, NULL, 0) + 1; + built = (char *)malloc(built_len); + if (!built) + { + LOG_ERROR(NULL, "*** Unexpected memory allocation failure: %d bytes", built_len); + return 1; + } + + vc_uri_build(uri, built, built_len); + + if (strcmp(built, expected) != 0) + { + LOG_ERROR(NULL, "*** Built URI <%s>\nexpected <%s>", built, expected); + free(built); + return 1; + } + + free(built); + + return 0; +} + +static int check_null_uri_pointer(void) +{ + int error_count = 0; + char buffer[1]; + + /* Check NULL URI can be passed without failure to all routines */ + vc_uri_release( NULL ); + vc_uri_clear( NULL ); + if (vc_uri_parse( NULL, NULL )) + error_count++; + if (vc_uri_parse( NULL, "" )) + error_count++; + if (vc_uri_build( NULL, NULL, 0 ) != 0) + error_count++; + buffer[0] = TEST_CHAR; + if (vc_uri_build( NULL, buffer, sizeof(buffer) ) != 0) + error_count++; + if (buffer[0] != TEST_CHAR) + error_count++; + if (vc_uri_scheme( NULL )) + error_count++; + if (vc_uri_userinfo( NULL )) + error_count++; + if (vc_uri_host( NULL )) + error_count++; + if (vc_uri_port( NULL )) + error_count++; + if (vc_uri_path( NULL )) + error_count++; + if (vc_uri_fragment( NULL )) + error_count++; + if (vc_uri_num_queries( NULL ) != 0) + error_count++; + vc_uri_query( NULL, 0, NULL, NULL ); + if (vc_uri_set_scheme( NULL, NULL )) + error_count++; + if (vc_uri_set_userinfo( NULL, NULL )) + error_count++; + if (vc_uri_set_host( NULL, NULL )) + error_count++; + if (vc_uri_set_port( NULL, NULL )) + error_count++; + if (vc_uri_set_path( NULL, NULL )) + error_count++; + if (vc_uri_set_fragment( NULL, NULL )) + error_count++; + if (vc_uri_add_query( NULL, NULL, NULL )) + error_count++; + + if (error_count) + LOG_ERROR(NULL, "NULL URI parameter testing failed"); + + return error_count; +} + +static int check_parse_parameters(VC_URI_PARTS_T *uri) +{ + int error_count = 0; + + if (vc_uri_parse( uri, NULL )) + { + LOG_ERROR(NULL, "Parsing NULL URI failed"); + error_count++; + } + + return error_count; +} + +static int check_build_parameters(VC_URI_PARTS_T *uri) +{ + int error_count = 0; + char buffer[1]; + + vc_uri_set_path( uri, TEST_STRING ); + + if (vc_uri_build( uri, NULL, 0 ) != strlen(TEST_STRING)) + { + LOG_ERROR(NULL, "Retrieving URI length failed"); + error_count++; + } + + buffer[0] = TEST_CHAR; + if (vc_uri_build( uri, buffer, 1 ) != strlen(TEST_STRING)) + { + LOG_ERROR(NULL, "Building URI to small buffer failed"); + error_count++; + } + if (buffer[0] != TEST_CHAR) + { + LOG_ERROR(NULL, "Building URI to small buffer modified buffer"); + error_count++; + } + + vc_uri_set_path( uri, NULL ); /* Reset uri */ + + return error_count; +} + +static int check_get_defaults(VC_URI_PARTS_T *uri) +{ + int error_count = 0; + const char *name = NULL, *value = NULL; + char buffer[1]; + + if (vc_uri_scheme( uri )) + error_count++; + if (vc_uri_userinfo( uri )) + error_count++; + if (vc_uri_host( uri )) + error_count++; + if (vc_uri_port( uri )) + error_count++; + if (vc_uri_path( uri )) + error_count++; + if (vc_uri_fragment( uri )) + error_count++; + if (vc_uri_num_queries( uri ) != 0) + error_count++; + + vc_uri_query( uri, 0, &name, &value ); + if (name != NULL || value != NULL) + error_count++; + + if (vc_uri_build(uri, NULL, 0) != 0) + error_count++; + buffer[0] = ~*TEST_STRING; /* Initialize with something */ + vc_uri_build(uri, buffer, sizeof(buffer)); + if (buffer[0] != '\0') /* Expect empty string */ + error_count++; + + if (error_count) + LOG_ERROR(NULL, "Getting default values gave unexpected values"); + + return error_count; +} + +static int check_accessors(VC_URI_PARTS_T *uri) +{ + int error_count = 0; + const char *str; + + if (vc_uri_set_scheme( uri, TEST_STRING )) + { + str = vc_uri_scheme(uri); + if (!str || strcmp(TEST_STRING, str)) + error_count++; + if (!vc_uri_set_scheme( uri, NULL )) + error_count++; + if (vc_uri_scheme(uri)) + error_count++; + } else + error_count++; + + if (vc_uri_set_userinfo( uri, TEST_STRING )) + { + str = vc_uri_userinfo(uri); + if (!str || strcmp(TEST_STRING, str)) + error_count++; + if (!vc_uri_set_userinfo( uri, NULL )) + error_count++; + if (vc_uri_userinfo(uri)) + error_count++; + } else + error_count++; + + if (vc_uri_set_host( uri, TEST_STRING )) + { + str = vc_uri_host(uri); + if (!str || strcmp(TEST_STRING, str)) + error_count++; + if (!vc_uri_set_host( uri, NULL )) + error_count++; + if (vc_uri_host(uri)) + error_count++; + } else + error_count++; + + if (vc_uri_set_port( uri, TEST_STRING )) + { + str = vc_uri_port(uri); + if (!str || strcmp(TEST_STRING, str)) + error_count++; + if (!vc_uri_set_port( uri, NULL )) + error_count++; + if (vc_uri_port(uri)) + error_count++; + } else + error_count++; + + if (vc_uri_set_path( uri, TEST_STRING )) + { + str = vc_uri_path(uri); + if (!str || strcmp(TEST_STRING, str)) + error_count++; + if (!vc_uri_set_path( uri, NULL )) + error_count++; + if (vc_uri_path(uri)) + error_count++; + } else + error_count++; + + if (vc_uri_set_fragment( uri, TEST_STRING )) + { + str = vc_uri_fragment(uri); + if (!str || strcmp(TEST_STRING, str)) + error_count++; + if (!vc_uri_set_fragment( uri, NULL )) + error_count++; + if (vc_uri_fragment(uri)) + error_count++; + } else + error_count++; + + if (vc_uri_add_query( uri, NULL, NULL )) + error_count++; + if (vc_uri_add_query( uri, NULL, TEST_VALUE )) + error_count++; + if (!vc_uri_add_query( uri, TEST_STRING, NULL )) + error_count++; + if (!vc_uri_add_query( uri, TEST_NAME, TEST_VALUE )) + error_count++; + + if (vc_uri_num_queries(uri) == 2) + { + const char *name = NULL, *value = NULL; + + vc_uri_query(uri, 0, &name, &value); + if (!name || strcmp(TEST_STRING, name)) + error_count++; + if (value) + error_count++; + + vc_uri_query(uri, 1, &name, &value); + if (!name || strcmp(TEST_NAME, name)) + error_count++; + if (!value || strcmp(TEST_VALUE, value)) + error_count++; + } else + error_count++; + + if (error_count) + LOG_ERROR(NULL, "Accessors failed"); + + return error_count; +} + +/** Test parameter validation + * + * \param uri Pre-created URI structure. + * \return 1 on error, 0 on success. */ +static int test_parameter_validation(VC_URI_PARTS_T *uri) +{ + int error_count = 0; + + error_count += check_null_uri_pointer(); + error_count += check_get_defaults(uri); + error_count += check_parse_parameters(uri); + error_count += check_build_parameters(uri); + error_count += check_accessors(uri); + + return error_count; +} + +/** Test parsing and rebuilding a URI. + * + * \param uri Pre-created URI structure. + * \param original The original URI string. + * \param expected The expected, re-built string, or NULL if original is expected. + * \return 1 on error, 0 on success. */ +static int test_parsing_uri(VC_URI_PARTS_T *uri, const char *original, const char *expected) +{ + bool parsed; + + LOG_INFO(NULL, "URI: <%s>", original); + + parsed = vc_uri_parse( uri, original ); + if (!parsed) + { + LOG_ERROR(NULL, "*** Expected <%s> to parse, but it didn't", original); + return 1; + } + + dump_uri(uri); + + return check_uri(uri, expected ? expected : original); +} + +/** Test building a URI from component parts. + * + * \param uri Pre-created URI structure. + * \param uri_data The data for building the URI and the expected output. + * \return 1 on error, 0 on success. */ +static int test_building_uri(VC_URI_PARTS_T *uri, BUILD_URI_T *uri_data) +{ + const char **p_str; + const char *name, *value; + + LOG_INFO(NULL, "Building URI <%s>", uri_data->expected_uri); + + vc_uri_clear(uri); + + if (!vc_uri_set_scheme(uri, uri_data->scheme)) + { + LOG_ERROR(NULL, "*** Failed to set scheme"); + return 1; + } + + if (!vc_uri_set_userinfo(uri, uri_data->userinfo)) + { + LOG_ERROR(NULL, "*** Failed to set userinfo"); + return 1; + } + + if (!vc_uri_set_host(uri, uri_data->host)) + { + LOG_ERROR(NULL, "*** Failed to set host"); + return 1; + } + + if (!vc_uri_set_port(uri, uri_data->port)) + { + LOG_ERROR(NULL, "*** Failed to set port"); + return 1; + } + + if (!vc_uri_set_path(uri, uri_data->path)) + { + LOG_ERROR(NULL, "*** Failed to set path"); + return 1; + } + + if (!vc_uri_set_fragment(uri, uri_data->fragment)) + { + LOG_ERROR(NULL, "*** Failed to set fragment"); + return 1; + } + + p_str = uri_data->queries; + name = *p_str++; + + while (name) + { + value = *p_str++; + if (!vc_uri_add_query(uri, name, value)) + { + LOG_ERROR(NULL, "*** Failed to add query"); + return 1; + } + name = *p_str++; + } + + dump_uri(uri); + + return check_uri(uri, uri_data->expected_uri); +} + +/** Test merging a relative URI with a base URI. + * + * \param uri Pre-created URI structure. + * \param uri_data The nase and relative URIs and the expected output. + * \return 1 on error, 0 on success. */ +static int test_merging_uri(VC_URI_PARTS_T *uri, MERGE_URI_T *uri_data) +{ + VC_URI_PARTS_T *base_uri; + + LOG_INFO(NULL, "Base <%s>, relative <%s>, expect <%s>", uri_data->base, uri_data->relative, uri_data->merged); + + vc_uri_clear(uri); + base_uri = vc_uri_create(); + if (!base_uri) + { + LOG_ERROR(NULL, "*** Failed to allocate base URI structure"); + return 1; + } + + if (!vc_uri_parse(base_uri, uri_data->base)) + { + LOG_ERROR(NULL, "*** Failed to parse base URI structure"); + return 1; + } + if (!vc_uri_parse(uri, uri_data->relative)) + { + LOG_ERROR(NULL, "*** Failed to parse relative URI structure"); + return 1; + } + + if (!vc_uri_merge(base_uri, uri)) + { + LOG_ERROR(NULL, "*** Failed to merge base and relative URIs"); + return 1; + } + + vc_uri_release(base_uri); + + return check_uri(uri, uri_data->merged); +} + +int main(int argc, char **argv) +{ + VC_URI_PARTS_T *uri; + int error_count = 0; + size_t ii; + + uri = vc_uri_create(); + if (!uri) + { + LOG_ERROR(NULL, "*** Failed to create URI structure."); + return 1; + } + + LOG_INFO(NULL, "Test parameter validation"); + error_count += test_parameter_validation(uri); + + LOG_INFO(NULL, "Test parsing URIs:"); + for (ii = 0; ii < ARRAY_SIZE(test_parse_uris); ii++) + { + error_count += test_parsing_uri(uri, test_parse_uris[ii].before, test_parse_uris[ii].after); + } + + LOG_INFO(NULL, "Test building URIs:"); + for (ii = 0; ii < ARRAY_SIZE(test_build_uris); ii++) + { + error_count += test_building_uri(uri, &test_build_uris[ii]); + } + + LOG_INFO(NULL, "Test merging URIs:"); + for (ii = 0; ii < ARRAY_SIZE(test_merge_uris); ii++) + { + error_count += test_merging_uri(uri, &test_merge_uris[ii]); + } + + if (argc > 1) + { + LOG_INFO(NULL, "Test parsing URIs from command line:"); + + while (argc-- > 1) + { + /* Test URIs passed on the command line are expected to parse and to + * match themselves when rebuilt. */ + error_count += test_parsing_uri(uri, argv[argc], NULL); + } + } + + vc_uri_release(uri); + + if (error_count) + LOG_ERROR(NULL, "*** %d errors reported", error_count); + +#ifdef _MSC_VER + LOG_INFO(NULL, "Press return to complete test."); + getchar(); +#endif + + return error_count; +} diff --git a/containers/test/uri_pipe.c b/containers/test/uri_pipe.c new file mode 100644 index 0000000..6dd6072 --- /dev/null +++ b/containers/test/uri_pipe.c @@ -0,0 +1,116 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "containers/containers.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_io.h" + +#include "nb_io.h" + +#define MAX_BUFFER_SIZE 2048 + +int main(int argc, char **argv) +{ + char buffer[MAX_BUFFER_SIZE]; + VC_CONTAINER_IO_T *read_io, *write_io; + VC_CONTAINER_STATUS_T status; + size_t received; + bool ready = true; + + if (argc < 3) + { + LOG_INFO(NULL, "Usage:\n%s \n", argv[0]); + return 1; + } + + read_io = vc_container_io_open(argv[1], VC_CONTAINER_IO_MODE_READ, &status); + if (!read_io) + { + LOG_INFO(NULL, "Opening <%s> for read failed: %d\n", argv[1], status); + return 2; + } + + write_io = vc_container_io_open(argv[2], VC_CONTAINER_IO_MODE_WRITE, &status); + if (!write_io) + { + vc_container_io_close(read_io); + LOG_INFO(NULL, "Opening <%s> for write failed: %d\n", argv[2], status); + return 3; + } + + nb_set_nonblocking_input(1); + + while (ready) + { + size_t total_written = 0; + + received = vc_container_io_read(read_io, buffer, sizeof(buffer)); + while (ready && total_written < received) + { + total_written += vc_container_io_write(write_io, buffer + total_written, received - total_written); + ready &= (write_io->status == VC_CONTAINER_SUCCESS); + } + ready &= (read_io->status == VC_CONTAINER_SUCCESS); + + if (nb_char_available()) + { + char c = nb_get_char(); + + switch (c) + { + case 'q': + case 'Q': + case 0x04: /* CTRL+D */ + case 0x1A: /* CTRL+Z */ + case 0x1B: /* Escape */ + ready = false; + break; + default: + ;/* Do nothing */ + } + } + } + + if (read_io->status != VC_CONTAINER_SUCCESS && read_io->status != VC_CONTAINER_ERROR_EOS) + { + LOG_INFO(NULL, "Read failed: %d\n", read_io->status); + } + + if (write_io->status != VC_CONTAINER_SUCCESS) + { + LOG_INFO(NULL, "Write failed: %d\n", write_io->status); + } + + vc_container_io_close(read_io); + vc_container_io_close(write_io); + + nb_set_nonblocking_input(0); + + return 0; +} diff --git a/containers/wav/CMakeLists.txt b/containers/wav/CMakeLists.txt new file mode 100644 index 0000000..aed61c6 --- /dev/null +++ b/containers/wav/CMakeLists.txt @@ -0,0 +1,13 @@ +# Container module needs to go in as a plugins so different prefix +# and install path +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +# Make sure the compiler can find the necessary include files +include_directories (../..) + +add_library(reader_wav ${LIBRARY_TYPE} wav_reader.c) + +target_link_libraries(reader_wav containers) + +install(TARGETS reader_wav DESTINATION ${VMCS_PLUGIN_DIR}) + diff --git a/containers/wav/wav_reader.c b/containers/wav/wav_reader.c new file mode 100644 index 0000000..0b279ba --- /dev/null +++ b/containers/wav/wav_reader.c @@ -0,0 +1,366 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include + +#define CONTAINER_IS_LITTLE_ENDIAN +//#define ENABLE_CONTAINERS_LOG_FORMAT +//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE +#define CONTAINER_HELPER_LOG_INDENT(a) 0 +#include "containers/core/containers_private.h" +#include "containers/core/containers_io_helpers.h" +#include "containers/core/containers_utils.h" +#include "containers/core/containers_logging.h" +#include "containers/core/containers_waveformat.h" + +/****************************************************************************** +Defines. +******************************************************************************/ +#define WAV_EXTRADATA_MAX 16 +#define BLOCK_SIZE (16*1024) + +/****************************************************************************** +GUID list for the different codecs +******************************************************************************/ +static const GUID_T atracx_guid = {0xbfaa23e9, 0x58cb, 0x7144, {0xa1, 0x19, 0xff, 0xfa, 0x01, 0xe4, 0xce, 0x62}}; +static const GUID_T pcm_guid = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + +/****************************************************************************** +Type definitions +******************************************************************************/ +typedef struct VC_CONTAINER_MODULE_T +{ + uint64_t data_offset; /**< Offset to the start of the data packets */ + int64_t data_size; /**< Size of the data contained in the data element */ + uint32_t block_size; /**< Size of a block of audio data */ + int64_t position; + uint64_t frame_data_left; + + VC_CONTAINER_TRACK_T *track; + uint8_t extradata[WAV_EXTRADATA_MAX]; + +} VC_CONTAINER_MODULE_T; + +/****************************************************************************** +Function prototypes +******************************************************************************/ +VC_CONTAINER_STATUS_T wav_reader_open( VC_CONTAINER_T * ); + +/****************************************************************************** +Local Functions +******************************************************************************/ + +/***************************************************************************** +Functions exported as part of the Container Module API + *****************************************************************************/ + +static VC_CONTAINER_STATUS_T wav_reader_read( VC_CONTAINER_T *p_ctx, + VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + uint32_t packet_flags = 0, size, data_size; + int64_t pts; + + pts = module->position * 8000000 / p_ctx->tracks[0]->format->bitrate; + data_size = module->frame_data_left; + if(!data_size) + { + data_size = module->block_size; + packet_flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; + } + module->frame_data_left = 0; + + if(module->position + data_size > module->data_size) + data_size = module->data_size - module->position; + if(data_size == 0) return VC_CONTAINER_ERROR_EOS; + + if((flags & VC_CONTAINER_READ_FLAG_SKIP) && !(flags & VC_CONTAINER_READ_FLAG_INFO)) /* Skip packet */ + { + size = SKIP_BYTES(p_ctx, data_size); + module->frame_data_left = data_size - size; + module->position += size; + return STREAM_STATUS(p_ctx); + } + + p_packet->flags = packet_flags; + p_packet->dts = p_packet->pts = pts; + p_packet->track = 0; + + if(flags & VC_CONTAINER_READ_FLAG_SKIP) + { + size = SKIP_BYTES(p_ctx, data_size); + module->frame_data_left = data_size - size; + if(!module->frame_data_left) p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + module->position += size; + p_packet->size += size; + return STREAM_STATUS(p_ctx); + } + + if(flags & VC_CONTAINER_READ_FLAG_INFO) + return VC_CONTAINER_SUCCESS; + + size = MIN(data_size, p_packet->buffer_size - p_packet->size); + size = READ_BYTES(p_ctx, p_packet->data, size); + module->frame_data_left = data_size - size; + if(!module->frame_data_left) p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; + module->position += size; + p_packet->size += size; + + return STREAM_STATUS(p_ctx); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T wav_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *p_offset, + VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + int64_t position; + VC_CONTAINER_PARAM_UNUSED(mode); + VC_CONTAINER_PARAM_UNUSED(flags); + + position = *p_offset * p_ctx->tracks[0]->format->bitrate / 8000000; + if(p_ctx->tracks[0]->format->type->audio.block_align) + position = position / p_ctx->tracks[0]->format->type->audio.block_align * + p_ctx->tracks[0]->format->type->audio.block_align; + if(position > module->data_size) position = module->data_size; + + module->position = position; + module->frame_data_left = 0; + + if(position >= module->data_size) return VC_CONTAINER_ERROR_EOS; + return SEEK(p_ctx, module->data_offset + position); +} + +/*****************************************************************************/ +static VC_CONTAINER_STATUS_T wav_reader_close( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; + unsigned int i; + + for(i = 0; i < p_ctx->tracks_num; i++) + vc_container_free_track(p_ctx, p_ctx->tracks[i]); + free(module); + return VC_CONTAINER_SUCCESS; +} + +/*****************************************************************************/ +VC_CONTAINER_STATUS_T wav_reader_open( VC_CONTAINER_T *p_ctx ) +{ + VC_CONTAINER_MODULE_T *module = 0; + VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; + VC_CONTAINER_FOURCC_T codec; + int64_t chunk_size, chunk_pos; + uint32_t format, channels, samplerate, bitrate, block_align, bps, cbsize = 0; + uint8_t buffer[12]; + + /* Check the RIFF chunk descriptor */ + if( PEEK_BYTES(p_ctx, buffer, 12) != 12 ) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if( VC_FOURCC(buffer[0], buffer[1], buffer[2], buffer[3]) != + VC_FOURCC('R','I','F','F') ) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + if( VC_FOURCC(buffer[8], buffer[9], buffer[10], buffer[11]) != + VC_FOURCC('W','A','V','E') ) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; + + /* + * We are dealing with a WAV file + */ + SKIP_FOURCC(p_ctx, "Chunk ID"); + SKIP_U32(p_ctx, "Chunk size"); + SKIP_FOURCC(p_ctx, "WAVE ID"); + + /* We're looking for the 'fmt' sub-chunk */ + do { + chunk_pos = STREAM_POSITION(p_ctx) + 8; + if( READ_FOURCC(p_ctx, "Chunk ID") == VC_FOURCC('f','m','t',' ') ) break; + + /* Not interested in this chunk. Skip it. */ + chunk_size = READ_U32(p_ctx, "Chunk size"); + SKIP_BYTES(p_ctx, chunk_size); + } while(STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS); + + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) + return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; /* 'fmt' not found */ + + /* Parse the 'fmt' sub-chunk */ + chunk_size = READ_U32(p_ctx, "Chunk size"); + format = READ_U16(p_ctx, "wFormatTag"); + channels = READ_U16(p_ctx, "nChannels"); + samplerate = READ_U32(p_ctx, "nSamplesPerSec"); + bitrate = READ_U32(p_ctx, "nAvgBytesPerSec") * 8; + block_align = READ_U16(p_ctx, "nBlockAlign"); + bps = READ_U16(p_ctx, "wBitsPerSample"); + + if(STREAM_POSITION(p_ctx) - chunk_pos <= chunk_size - 2) + cbsize = READ_U16(p_ctx, "cbSize"); + + if(format == WAVE_FORMAT_EXTENSIBLE && + chunk_size >= 18 + 22 && cbsize >= 22) + { + GUID_T guid; + codec = VC_CONTAINER_CODEC_UNKNOWN; + + SKIP_U16(p_ctx, "wValidBitsPerSample"); + SKIP_U32(p_ctx, "dwChannelMask"); + READ_GUID(p_ctx, &guid, "SubFormat"); + + if(!memcmp(&guid, &pcm_guid, sizeof(guid))) + codec = VC_CONTAINER_CODEC_PCM_SIGNED_LE; + else if(!memcmp(&guid, &atracx_guid, sizeof(guid))) + codec = VC_CONTAINER_CODEC_ATRAC3; + + cbsize -= 22; + + /* TODO: deal with channel mapping */ + } + else + { + codec = waveformat_to_codec(format); + } + + /* Bail out if we don't recognise the codec */ + if(codec == VC_CONTAINER_CODEC_UNKNOWN) + return VC_CONTAINER_ERROR_FORMAT_FEATURE_NOT_SUPPORTED; + + /* Do some sanity checking on the info we got */ + if(!channels || !samplerate || !block_align || !bitrate) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + if(codec == VC_CONTAINER_CODEC_ATRAC3 && channels > 2) + return VC_CONTAINER_ERROR_FORMAT_INVALID; + + /* Allocate our context */ + module = malloc(sizeof(*module)); + if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } + memset(module, 0, sizeof(*module)); + p_ctx->priv->module = module; + p_ctx->tracks_num = 1; + p_ctx->tracks = &module->track; + p_ctx->tracks[0] = vc_container_allocate_track(p_ctx, 0); + if(!p_ctx->tracks[0]) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; + + p_ctx->tracks[0]->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; + p_ctx->tracks[0]->format->codec = codec; + p_ctx->tracks[0]->format->type->audio.channels = channels; + p_ctx->tracks[0]->format->type->audio.sample_rate = samplerate; + p_ctx->tracks[0]->format->type->audio.block_align = block_align; + p_ctx->tracks[0]->format->type->audio.bits_per_sample = bps; + p_ctx->tracks[0]->format->bitrate = bitrate; + p_ctx->tracks[0]->is_enabled = true; + p_ctx->tracks[0]->format->extradata_size = 0; + p_ctx->tracks[0]->format->extradata = module->extradata; + module->block_size = block_align; + + /* Prepare the codec extradata */ + if(codec == VC_CONTAINER_CODEC_ATRAC3) + { + uint16_t h, mode; + + SKIP_U16(p_ctx, "len"); + SKIP_U16(p_ctx, "layer"); + SKIP_U32(p_ctx, "bytes_per_frame"); + mode = READ_U16(p_ctx, "mode"); + SKIP_U16(p_ctx, "mode_ext"); + SKIP_U16(p_ctx, "num_subframes"); + SKIP_U16(p_ctx, "flags"); + + h = (1 << 15); + if(channels == 2) + { + h |= (1 << 13); + if(mode == 1) h |= (1 << 14); + } + h |= block_align & 0x7ff; + + p_ctx->tracks[0]->format->extradata[0] = h >> 8; + p_ctx->tracks[0]->format->extradata[1] = h & 255; + p_ctx->tracks[0]->format->extradata_size = 2; + } + else if(codec == VC_CONTAINER_CODEC_ATRACX && cbsize >= 6) + { + SKIP_BYTES(p_ctx, 2); + p_ctx->tracks[0]->format->extradata_size = + READ_BYTES(p_ctx, p_ctx->tracks[0]->format->extradata, 6); + } + else if(codec == VC_CONTAINER_CODEC_PCM_SIGNED_LE) + { + /* Audioplus can no longer be given anything other than a multiple-of-16 number of samples */ + block_align *= 16; + module->block_size = (BLOCK_SIZE / block_align) * block_align; + } + + /* Skip the rest of the 'fmt' sub-chunk */ + SKIP_BYTES(p_ctx, chunk_pos + chunk_size - STREAM_POSITION(p_ctx)); + + /* We also need the 'data' sub-chunk */ + do { + chunk_pos = STREAM_POSITION(p_ctx) + 8; + if( READ_FOURCC(p_ctx, "Chunk ID") == VC_FOURCC('d','a','t','a') ) break; + + /* Not interested in this chunk. Skip it. */ + chunk_size = READ_U32(p_ctx, "Chunk size"); + SKIP_BYTES(p_ctx, chunk_size); + } while(STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS); + + if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) + { + status = VC_CONTAINER_ERROR_FORMAT_INVALID; /* 'data' not found */; + goto error; + } + + module->data_offset = chunk_pos; + module->data_size = READ_U32(p_ctx, "Chunk size"); + p_ctx->duration = module->data_size * 8000000 / bitrate; + if(STREAM_SEEKABLE(p_ctx)) + p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; + + /* + * We now have all the information we really need to start playing the stream + */ + + p_ctx->priv->pf_close = wav_reader_close; + p_ctx->priv->pf_read = wav_reader_read; + p_ctx->priv->pf_seek = wav_reader_seek; + + /* Seek back to the start of the data */ + status = SEEK(p_ctx, module->data_offset); + if(status != VC_CONTAINER_SUCCESS) goto error; + return VC_CONTAINER_SUCCESS; + + error: + LOG_DEBUG(p_ctx, "wav: error opening stream (%i)", status); + if(module) wav_reader_close(p_ctx); + return status; +} + +/******************************************************************************** + Entrypoint function + ********************************************************************************/ + +#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) +# pragma weak reader_open wav_reader_open +#endif diff --git a/host_support/include/vc_mem.h b/host_support/include/vc_mem.h new file mode 100755 index 0000000..0b5a53c --- /dev/null +++ b/host_support/include/vc_mem.h @@ -0,0 +1,51 @@ +/* +Copyright (c) 2013, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#if !defined( VC_MEM_H ) +#define VC_MEM_H + +#include + +#define VC_MEM_IOC_MAGIC 'v' + +#define VC_MEM_IOC_MEM_PHYS_ADDR _IOR( VC_MEM_IOC_MAGIC, 0, unsigned long ) +#define VC_MEM_IOC_MEM_SIZE _IOR( VC_MEM_IOC_MAGIC, 1, unsigned int ) +#define VC_MEM_IOC_MEM_BASE _IOR( VC_MEM_IOC_MAGIC, 2, unsigned int ) +#define VC_MEM_IOC_MEM_LOAD _IOR( VC_MEM_IOC_MAGIC, 3, unsigned int ) + +#if defined( __KERNEL__ ) +#define VC_MEM_TO_ARM_ADDR_MASK 0x3FFFFFFF + +extern unsigned long mm_vc_mem_phys_addr; +extern unsigned int mm_vc_mem_size; +extern unsigned int mm_vc_mem_base; +extern int vc_mem_get_current_size( void ); +extern int vc_mem_get_current_base( void ); +extern int vc_mem_access_mem( int write_mem, void *buf, uint32_t vc_mem_addr, size_t num_bytes ); +#endif + +#endif /* VC_MEM_H */ + diff --git a/interface/mmal/CMakeLists.txt b/interface/mmal/CMakeLists.txt index 0da814d..5b063b4 100644 --- a/interface/mmal/CMakeLists.txt +++ b/interface/mmal/CMakeLists.txt @@ -1,4 +1,3 @@ - add_definitions(-Wall -Werror) add_library(mmal SHARED util/mmal_util.c) @@ -6,16 +5,11 @@ add_library(mmal SHARED util/mmal_util.c) add_subdirectory(core) add_subdirectory(util) add_subdirectory(vc) - -#add_subdirectory(test/lua) -#add_subdirectory(test/standalone) - -if(BUILD_MMAL_COMPONENTS) add_subdirectory(components) +add_subdirectory(openmaxil) +add_subdirectory(client) + target_link_libraries(mmal mmal_core mmal_util mmal_vc_client vcos mmal_components) -else(BUILD_MMAL_COMPONENTS) -target_link_libraries(mmal mmal_core mmal_util mmal_vc_client vcos) -endif(BUILD_MMAL_COMPONENTS) install(TARGETS mmal DESTINATION lib) install(FILES @@ -40,3 +34,8 @@ install(FILES mmal_types.h DESTINATION include/interface/mmal ) + +# Test apps +if(BUILD_MMAL_APPS) +add_subdirectory(test) +endif(BUILD_MMAL_APPS)