diff --git a/librmff/librmff.h b/librmff/librmff.h index ce7c40d66..dc9d8a7ce 100644 --- a/librmff/librmff.h +++ b/librmff/librmff.h @@ -151,6 +151,54 @@ free(buffer); } \endcode + + + \section packed_video_frames Packed video frames + + The RealVideo frames are not stored one-complete-frame in one packet + (packet meaning one 'frame' used by ::rmff_write_frame and returned by + ::rmff_read_next_frame). They are split up into several sub packets. + However, the Real decoder libraries usually need a completely assembled + frame that contains all sub packets along with the offsets to each sub + packet in the complete frame. + + \a librmff can make the developper's life easier because it provides + a function ::rmff_write_packed_video_frame that will split up such + an assembled packet into all the sub packets and write those + automatically. + + \subsection packed_format Bitstream format + + The bitstream for such a sub packet looks like this: + + AABBBBBB BCCCCCCC {DEFFFFFF FFFFFFFF (GGGGGGGG GGGGGGGG) + HIJJJJJJ JJJJJJJJ (KKKKKKKK KKKKKKKK) LLLLLLLL} + + - \c A, two bits: Sub packet type indicator + - \c 00 partial frame + - \c 01 whole frame. If this is the case then only the bits A, B + and \c C are present. In all the other cases the other bits are present + as well. + - \c 10 last partial frame + - \c 11 multiple frames + - \c B, seven bits: number of sub packets in the complete frame + - \c C, seven bits: current sub packet's sequence number starting at 1 + - \c D, one bit: unknown + - \c E, one bit: If set the \c G bits/bytes are not present. + - \c F, 14 bits: The complete frame's length in bytes. If \c E is NOT set + then the total length is (F << 16) | G, otherwise \c G is not present + and the total length is just \c F. + - \c H, one bit, unknown + - \c I, one bit: If set the \c L bits/bytes are not present. + - \c J, 14 bits: The current packet's offset in bytes. If \c J is NOT set + then the offset is (J << 16) | K, otherwise \c K is not present + and the offset is just \c J. Note that if \c AA is 10 then the offset is + to be interpreted from the end of the of the complete frame. In this case + it is equal to the current sub packet's length. + - \c L, 8 bits: The current frame's sequence number / frame number % 255. + Can be used for detecting frame boundaries if the sub packaging has failed + or the stream is broken. + - The rest is the video data. */ #ifndef __RMFF_H @@ -160,6 +208,8 @@ extern "C" { #endif +#include "os.h" + #include #include @@ -723,6 +773,22 @@ int rmff_fix_headers(rmff_file_t *file); */ int rmff_write_frame(rmff_track_t *track, rmff_frame_t *frame); +/** \brief Unpacks a packed video frame into sub packets and writes each. + + Please see the section about packed video \ref packed_video_frames frames + for a description of this function. + + This function takes such a complete frame, splits it up into their sub + packets and writes each sub packet out into the file this track belongs to. + + \param track The track this frame belongs to. + \param frame The assembled/packed video frame that is to be split up. + + \returns \c RMFF_ERR_OK on success or one of the other \c RMFF_ERR_* + constants on error. +*/ +int rmff_write_packed_video_frame(rmff_track_t *track, rmff_frame_t *frame); + /** \brief Creates an index at the end of the file. The application can request that an index is created For each track @@ -808,6 +874,17 @@ uint16_t rmff_get_uint16_be(const void *buf); */ uint32_t rmff_get_uint32_be(const void *buf); +/** \brief Reads a 32bit uint from an address. + + The uint is converted from little endian byte order to the machine's byte + order. + + \param buf The address to read from. + + \returns The 32bit uint converted to the machine's byte order. +*/ +uint32_t rmff_get_uint32_le(const void *buf); + /** \brief Write a 16bit uint at an address. The value is converted from the machine's byte order to big endian byte @@ -828,6 +905,26 @@ void rmff_put_uint16_be(void *buf, uint16_t value); */ void rmff_put_uint32_be(void *buf, uint32_t value); +/** \brief Write a 32bit uint at an address. + + The value is converted from the machine's byte order to little endian byte + order. + + \param buf The address to write to. + \param value The value to write. +*/ +void rmff_put_uint32_le(void *buf, uint32_t value); + +#if defined(ARCH_LITTLEENDIAN) +# define rmff_get_uint32_me(b) rmff_get_uint32_le(b) +# define rmff_put_uint32_me(b) rmff_put_uint32_le(b) +#elif defined(ARCH_BIGENDIAN) +# define rmff_get_uint32_me(b) rmff_get_uint32_be(b) +# define rmff_put_uint32_me(b) rmff_put_uint32_be(b) +#else +# error Neither ARCH_LITTLEENDIAN nor ARCH_BIGENDIAN has been defined. +#endif + #if defined(__cplusplus) } #endif diff --git a/librmff/rmff.c b/librmff/rmff.c index 6473144d5..53292891e 100644 --- a/librmff/rmff.c +++ b/librmff/rmff.c @@ -58,6 +58,7 @@ typedef struct rmff_track_internal_t { uint32_t avg_packet_size; uint32_t highest_timecode; uint32_t num_packets; + uint32_t num_packed_frames; int index_this; uint32_t total_bytes; rmff_bitrate_t bitrate; @@ -126,6 +127,21 @@ rmff_get_uint32_be(const void *buf) { return ret; } +uint32_t +rmff_get_uint32_le(const void *buf) { + uint32_t ret; + unsigned char *tmp; + + tmp = (unsigned char *) buf; + + ret = tmp[3] & 0xff; + ret = (ret << 8) + (tmp[2] & 0xff); + ret = (ret << 8) + (tmp[1] & 0xff); + ret = (ret << 8) + (tmp[0] & 0xff); + + return ret; +} + void rmff_put_uint16_be(void *buf, uint16_t value) { @@ -150,6 +166,19 @@ rmff_put_uint32_be(void *buf, tmp[0] = (value >>= 8) & 0xff; } +void +rmff_put_uint32_le(void *buf, + uint32_t value) { + unsigned char *tmp; + + tmp = (unsigned char *) buf; + + tmp[0] = value & 0xff; + tmp[1] = (value >>= 8) & 0xff; + tmp[2] = (value >>= 8) & 0xff; + tmp[3] = (value >>= 8) & 0xff; +} + #define read_uint8() file_read_uint8(io, fh) #define read_uint16_be() file_read_uint16_be(io, fh) #define read_uint32_be() file_read_uint32_be(io, fh) @@ -1379,3 +1408,124 @@ rmff_write_index(rmff_file_t *file) { return clear_error(); } + +int +rmff_write_packed_video_frame(rmff_track_t *track, + rmff_frame_t *frame) { + unsigned char *src_ptr, *ptr, *dst; + int num_subpackets, i, offset, total_len; + uint32_t *offsets, *lengths; + rmff_frame_t *spframe; + rmff_track_internal_t *tint; + + if ((track == NULL) || (frame == NULL) || + (track->file == NULL) || + (track->file->open_mode != RMFF_OPEN_MODE_WRITING)) + return set_error(RMFF_ERR_PARAMETERS, NULL, RMFF_ERR_PARAMETERS); + + tint = (rmff_track_internal_t *)track->internal; + src_ptr = frame->data; + num_subpackets = *src_ptr + 1; + src_ptr++; + if (frame->size < (num_subpackets * 8 + 1)) + return set_error(RMFF_ERR_DATA, "RealVideo unpacking failed: frame size " + "too small. Could not extract sub packet offsets.", + RMFF_ERR_DATA); + + offsets = (uint32_t *)safemalloc(num_subpackets * sizeof(uint32_t)); + for (i = 0; i < num_subpackets; i++) { + src_ptr += 4; + offsets[i] = rmff_get_uint32_me(src_ptr); + src_ptr += 4; + } + if ((offsets[num_subpackets - 1] + (src_ptr - frame->data)) >= frame->size) { + safefree(offsets); + return set_error(RMFF_ERR_DATA, "RealVideo unpacking failed: frame size " + "too small. The sub packet offsets indicate a size " + "larger than the actual size.", RMFF_ERR_DATA); + } + total_len = frame->size - (src_ptr - frame->data); + lengths = (uint32_t *)safemalloc(num_subpackets * sizeof(uint32_t)); + for (i = 0; i < (num_subpackets - 1); i++) + lengths[i] = offsets[i + 1] - offsets[i]; + lengths[num_subpackets - 1] = total_len - offsets[num_subpackets - 1]; + + dst = (unsigned char *)safemalloc(frame->size * 2); + for (i = 0; i < num_subpackets; i++) { + ptr = dst; + if (num_subpackets == 1) { + /* BROKEN! */ + *ptr = 0xc0; /* complete frame */ + ptr++; + + } else { + *ptr = (num_subpackets >> 1) & 0x7f; /* number of subpackets */ + if (i == (num_subpackets - 1)) /* last fragment? */ + *ptr |= 0x80; + ptr++; + + *ptr = i + 1; /* fragment number */ + *ptr |= ((num_subpackets & 0x01) << 7); /* number of subpackets */ + ptr++; + } + + /* total packet length: */ + if (total_len > 0x3fff) { + rmff_put_uint16_be(ptr, ((total_len & 0x3fff0000) >> 16)); + ptr += 2; + rmff_put_uint16_be(ptr, total_len & 0x0000ffff); + } else + rmff_put_uint16_be(ptr, 0x4000 | total_len); + ptr += 2; + + /* fragment offset from beginning/end: */ + if (num_subpackets == 1) + offset = frame->timecode; + else if (i < (num_subpackets - 1)) + offset = offsets[i]; + else + /* If it's the last packet then the 'offset' is the fragment's length. */ + offset = lengths[i]; + + if (offset > 0x3fff) { + rmff_put_uint16_be(ptr, ((offset & 0x3fff0000) >> 16)); + ptr += 2; + rmff_put_uint16_be(ptr, offset & 0x0000ffff); + } else + rmff_put_uint16_be(ptr, 0x4000 | offset); + ptr += 2; + + /* sequence number = frame number & 0xff */ + *ptr = tint->num_packed_frames & 0xff; + ptr++; + + memcpy(ptr, src_ptr, lengths[i]); + src_ptr += lengths[i]; + ptr += lengths[i]; + + spframe = rmff_allocate_frame(ptr - dst, dst); + if (spframe == NULL) { + safefree(offsets); + safefree(lengths); + safefree(dst); + return set_error(RMFF_ERR_IO, "Memory allocation error: Could not get a " + "rmff_frame_t", RMFF_ERR_IO); + } + spframe->timecode = frame->timecode; + spframe->flags = frame->flags; + if (rmff_write_frame(track, spframe) != RMFF_ERR_OK) { + safefree(offsets); + safefree(lengths); + safefree(dst); + return rmff_last_error; + } + rmff_release_frame(spframe); + } + safefree(offsets); + safefree(lengths); + safefree(dst); + + tint->num_packed_frames++; + + return set_error(RMFF_ERR_OK, NULL, RMFF_ERR_OK); +}